1//////////////////////////////////////////////////////////////////////
   2// LibFile: shapes3d.scad
   3//   Some standard modules for making 3d shapes with attachment support, and function forms
   4//   that produce a VNF.  Also included are shortcuts cylinders in each orientation and extended versions of
   5//   the standard modules that provide roundovers and chamfers.  The spheroid() module provides
   6//   several different ways to make a sphere, and the text modules let you write text on a path
   7//   so you can place it on a curved object.  A ruler lets you measure objects.
   8// Includes:
   9//   include <BOSL2/std.scad>
  10// FileGroup: Basic Modeling
  11// FileSummary: Attachable cubes, cylinders, spheres, ruler, and text.  Many can produce a VNF.
  12// FileFootnotes: STD=Included in std.scad
  13//////////////////////////////////////////////////////////////////////
  14
  15use <builtins.scad>
  16
  17
  18// Section: Cuboids, Prismoids and Pyramids
  19
  20// Function&Module: cube()
  21// Synopsis: Creates a cube with anchors for attaching children.
  22// SynTags: Geom, VNF, Ext
  23// Topics: Shapes (3D), Attachable, VNF Generators
  24// See Also: cuboid(), prismoid()
  25// Usage: As Module (as in native OpenSCAD)
  26//   cube(size, [center]);
  27// Usage: With BOSL2 Attachment extensions
  28//   cube(size, [center], [anchor=], [spin=], [orient=]) [ATTACHMENTS];
  29// Usage: As Function (BOSL2 extension)
  30//   vnf = cube(size, ...);
  31// Description:
  32//   Creates a 3D cubic object.
  33//   This module extends the built-in cube()` module by providing support for attachments and a function form.  
  34//   When called as a function, returns a [VNF](vnf.scad) for a cube.
  35// Arguments:
  36//   size = The size of the cube.
  37//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=FRONT+LEFT+BOTTOM`.
  38//   ---
  39//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
  40//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
  41//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
  42// Example: Simple cube.
  43//   cube(40);
  44// Example: Rectangular cube.
  45//   cube([20,40,50]);
  46// Example: Anchoring.
  47//   cube([20,40,50], anchor=BOTTOM+FRONT);
  48// Example: Spin.
  49//   cube([20,40,50], anchor=BOTTOM+FRONT, spin=30);
  50// Example: Orientation.
  51//   cube([20,40,50], anchor=BOTTOM+FRONT, spin=30, orient=FWD);
  52// Example: Standard Connectors.
  53//   cube(40, center=true) show_anchors();
  54// Example: Called as Function
  55//   vnf = cube([20,40,50]);
  56//   vnf_polyhedron(vnf);
  57
  58module cube(size=1, center, anchor, spin=0, orient=UP)
  59{
  60    anchor = get_anchor(anchor, center, -[1,1,1], -[1,1,1]);
  61    size = scalar_vec3(size);
  62    attachable(anchor,spin,orient, size=size) {
  63        _cube(size, center=true);
  64        children();
  65    }
  66}
  67
  68function cube(size=1, center, anchor, spin=0, orient=UP) =
  69    let(
  70        siz = scalar_vec3(size)
  71    )
  72    assert(all_positive(siz), "All size components must be positive.")
  73    let(
  74        anchor = get_anchor(anchor, center, -[1,1,1], -[1,1,1]),
  75        unscaled = [
  76            [-1,-1,-1],[1,-1,-1],[1,1,-1],[-1,1,-1],
  77            [-1,-1, 1],[1,-1, 1],[1,1, 1],[-1,1, 1],
  78        ]/2,
  79        verts = is_num(size)? unscaled * size :
  80            is_vector(size,3)? [for (p=unscaled) v_mul(p,size)] :
  81            assert(is_num(size) || is_vector(size,3)),
  82        faces = [
  83            [0,1,2], [0,2,3],  //BOTTOM
  84            [0,4,5], [0,5,1],  //FRONT
  85            [1,5,6], [1,6,2],  //RIGHT
  86            [2,6,7], [2,7,3],  //BACK
  87            [3,7,4], [3,4,0],  //LEFT
  88            [6,4,7], [6,5,4]   //TOP
  89        ]
  90    ) [reorient(anchor,spin,orient, size=siz, p=verts), faces];
  91
  92
  93
  94// Module: cuboid()
  95// Synopsis: Creates a cube with chamfering and roundovers.
  96// SynTags: Geom
  97// Topics: Shapes (3D), Attachable, VNF Generators
  98// See Also: prismoid(), rounded_prism()
  99// Usage: Standard Cubes
 100//   cuboid(size, [anchor=], [spin=], [orient=]);
 101//   cuboid(size, p1=, ...);
 102//   cuboid(p1=, p2=, ...);
 103// Usage: Chamfered Cubes
 104//   cuboid(size, [chamfer=], [edges=], [except=], [trimcorners=], ...);
 105// Usage: Rounded Cubes
 106//   cuboid(size, [rounding=], [teardrop=], [edges=], [except=], [trimcorners=], ...);
 107// Usage: Attaching children
 108//   cuboid(...) ATTACHMENTS;
 109//
 110// Description:
 111//   Creates a cube or cuboid object, with optional chamfering or rounding of edges and corners.
 112//   You cannot mix chamfering and rounding: just one edge treatment with the same size applies to all selected edges.
 113//   Negative chamfers and roundings can be applied to create external fillets, but they
 114//   only apply to edges around the top or bottom faces.  If you specify an edge set other than "ALL"
 115//   with negative roundings or chamfers then you will get an error.  See [Specifying Edges](attachments.scad#section-specifying-edges)
 116//   for information on how to specify edge sets.
 117// Arguments:
 118//   size = The size of the cube, a number or length 3 vector.
 119//   ---
 120//   chamfer = Size of chamfer, inset from sides.  Default: No chamfering.
 121//   rounding = Radius of the edge rounding.  Default: No rounding.
 122//   edges = Edges to mask.  See [Specifying Edges](attachments.scad#section-specifying-edges).  Default: all edges.
 123//   except = Edges to explicitly NOT mask.  See [Specifying Edges](attachments.scad#section-specifying-edges).  Default: No edges.
 124//   trimcorners = If true, rounds or chamfers corners where three chamfered/rounded edges meet.  Default: `true`
 125//   teardrop = If given as a number, rounding around the bottom edge of the cuboid won't exceed this many degrees from vertical.  If true, the limit angle is 45 degrees.  Default: `false`
 126//   p1 = Align the cuboid's corner at `p1`, if given.  Forces `anchor=FRONT+LEFT+BOTTOM`.
 127//   p2 = If given with `p1`, defines the cornerpoints of the cuboid.
 128//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
 129//   spin = Rotate this many degrees around the Z axis.  See [spin](attachments.scad#subsection-spin).  Default: `0`
 130//   orient = Vector to rotate top towards.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
 131// Example: Simple regular cube.
 132//   cuboid(40);
 133// Example: Cube with minimum cornerpoint given.
 134//   cuboid(20, p1=[10,0,0]);
 135// Example: Rectangular cube, with given X, Y, and Z sizes.
 136//   cuboid([20,40,50]);
 137// Example: Cube by Opposing Corners.
 138//   cuboid(p1=[0,10,0], p2=[20,30,30]);
 139// Example: Chamferred Edges and Corners.
 140//   cuboid([30,40,50], chamfer=5);
 141// Example: Chamferred Edges, Untrimmed Corners.
 142//   cuboid([30,40,50], chamfer=5, trimcorners=false);
 143// Example: Rounded Edges and Corners
 144//   cuboid([30,40,50], rounding=10);
 145// Example(VPR=[100,0,25],VPD=180): Rounded Edges and Corners with Teardrop Bottoms
 146//   cuboid([30,40,50], rounding=10, teardrop=true);
 147// Example: Rounded Edges, Untrimmed Corners
 148//   cuboid([30,40,50], rounding=10, trimcorners=false);
 149// Example: Chamferring Selected Edges
 150//   cuboid(
 151//       [30,40,50], chamfer=5,
 152//       edges=[TOP+FRONT,TOP+RIGHT,FRONT+RIGHT],
 153//       $fn=24
 154//   );
 155// Example: Rounding Selected Edges
 156//   cuboid(
 157//       [30,40,50], rounding=5,
 158//       edges=[TOP+FRONT,TOP+RIGHT,FRONT+RIGHT],
 159//       $fn=24
 160//   );
 161// Example: Negative Chamferring
 162//   cuboid(
 163//       [30,40,50], chamfer=-5,
 164//       edges=[TOP,BOT], except=RIGHT,
 165//       $fn=24
 166//   );
 167// Example: Negative Chamferring, Untrimmed Corners
 168//   cuboid(
 169//       [30,40,50], chamfer=-5,
 170//       edges=[TOP,BOT], except=RIGHT,
 171//       trimcorners=false, $fn=24
 172//   );
 173// Example: Negative Rounding
 174//   cuboid(
 175//       [30,40,50], rounding=-5,
 176//       edges=[TOP,BOT], except=RIGHT,
 177//       $fn=24
 178//   );
 179// Example: Negative Rounding, Untrimmed Corners
 180//   cuboid(
 181//       [30,40,50], rounding=-5,
 182//       edges=[TOP,BOT], except=RIGHT,
 183//       trimcorners=false, $fn=24
 184//   );
 185// Example: Roundings and Chamfers can be as large as the full size of the cuboid, so long as the edges would not interfere.
 186//   cuboid([40,20,10], rounding=20, edges=[FWD+RIGHT,BACK+LEFT]);
 187// Example: Standard Connectors
 188//   cuboid(40) show_anchors();
 189
 190module cuboid(
 191    size=[1,1,1],
 192    p1, p2,
 193    chamfer,
 194    rounding,
 195    edges=EDGES_ALL,
 196    except=[],
 197    except_edges,
 198    trimcorners=true,
 199    teardrop=false,
 200    anchor=CENTER,
 201    spin=0,
 202    orient=UP
 203) {
 204    module trunc_cube(s,corner) {
 205        multmatrix(
 206            (corner.x<0? xflip() : ident(4)) *
 207            (corner.y<0? yflip() : ident(4)) *
 208            (corner.z<0? zflip() : ident(4)) *
 209            scale(s+[1,1,1]*0.001) *
 210            move(-[1,1,1]/2)
 211        ) polyhedron(
 212            [[1,1,1],[1,1,0],[1,0,0],[0,1,1],[0,1,0],[1,0,1],[0,0,1]],
 213            [[0,1,2],[2,5,0],[0,5,6],[0,6,3],[0,3,4],[0,4,1],[1,4,2],[3,6,4],[5,2,6],[2,4,6]]
 214        );
 215    }
 216    module xtcyl(l,r) {
 217        if (teardrop) {
 218            teardrop(r=r, l=l, cap_h=r, ang=teardrop, spin=90, orient=DOWN);
 219        } else {
 220            yrot(90) cyl(l=l, r=r);
 221        }
 222    }
 223    module ytcyl(l,r) {
 224        if (teardrop) {
 225            teardrop(r=r, l=l, cap_h=r, ang=teardrop, spin=0, orient=DOWN);
 226        } else {
 227            zrot(90) yrot(90) cyl(l=l, r=r);
 228        }
 229    }
 230    module tsphere(r) {
 231        if (teardrop) {
 232            onion(r=r, cap_h=r, ang=teardrop, orient=DOWN);
 233        } else {
 234            spheroid(r=r, style="octa", orient=DOWN);
 235        }
 236    }
 237    module corner_shape(corner) {
 238        e = _corner_edges(edges, corner);
 239        cnt = sum(e);
 240        r = first_defined([chamfer, rounding]);
 241        dummy = assert(is_finite(r) && !approx(r,0));
 242        c = [r,r,r];
 243        m = 0.01;
 244        c2 = v_mul(corner,c/2);
 245        c3 = v_mul(corner,c-[1,1,1]*m/2);
 246        $fn = is_finite(chamfer)? 4 : quantup(segs(r),4);
 247        translate(v_mul(corner, size/2-c)) {
 248            if (cnt == 0 || approx(r,0)) {
 249                translate(c3) cube(m, center=true);
 250            } else if (cnt == 1) {
 251                if (e.x) {
 252                    right(c3.x) {
 253                        intersection() {
 254                            xtcyl(l=m, r=r);
 255                            multmatrix(
 256                                (corner.y<0? yflip() : ident(4)) *
 257                                (corner.z<0? zflip() : ident(4))
 258                            ) {
 259                                yrot(-90) linear_extrude(height=m+0.1, center=true) {
 260                                    polygon([[r,0],[0.999*r,0],[0,0.999*r],[0,r],[r,r]]);
 261                                }
 262                            }
 263                        }
 264                    }
 265                } else if (e.y) {
 266                    back(c3.y) {
 267                        intersection() {
 268                            ytcyl(l=m, r=r);
 269                            multmatrix(
 270                                (corner.x<0? xflip() : ident(4)) *
 271                                (corner.z<0? zflip() : ident(4))
 272                            ) {
 273                                xrot(90) linear_extrude(height=m+0.1, center=true) {
 274                                    polygon([[r,0],[0.999*r,0],[0,0.999*r],[0,r],[r,r]]);
 275                                }
 276                            }
 277                        }
 278                    }
 279                } else if (e.z) {
 280                    up(c3.z) {
 281                        intersection() {
 282                            zcyl(l=m, r=r);
 283                            multmatrix(
 284                                (corner.x<0? xflip() : ident(4)) *
 285                                (corner.y<0? yflip() : ident(4))
 286                            ) {
 287                                linear_extrude(height=m+0.1, center=true) {
 288                                    polygon([[r,0],[0.999*r,0],[0,0.999*r],[0,r],[r,r]]);
 289                                }
 290                            }
 291                        }
 292                    }
 293                }
 294            } else if (cnt == 2) {
 295                intersection() {
 296                    if (!e.x) {
 297                        intersection() {
 298                            ytcyl(l=c.y*2, r=r);
 299                            zcyl(l=c.z*2, r=r);
 300                        }
 301                    } else if (!e.y) {
 302                        intersection() {
 303                            xtcyl(l=c.x*2, r=r);
 304                            zcyl(l=c.z*2, r=r);
 305                        }
 306                    } else {
 307                        intersection() {
 308                            xtcyl(l=c.x*2, r=r);
 309                            ytcyl(l=c.y*2, r=r);
 310                        }
 311                    }
 312                    translate(c2) trunc_cube(c,corner); // Trim to just the octant.
 313                }
 314            } else {
 315                intersection() {
 316                    if (trimcorners) {
 317                        tsphere(r=r);
 318                    } else {
 319                        intersection() {
 320                            xtcyl(l=c.x*2, r=r);
 321                            ytcyl(l=c.y*2, r=r);
 322                            zcyl(l=c.z*2, r=r);
 323                        }
 324                    }
 325                    translate(c2) trunc_cube(c,corner); // Trim to just the octant.
 326                }
 327            }
 328        }
 329    }
 330
 331    size = scalar_vec3(size);
 332    edges = _edges(edges, except=first_defined([except_edges,except]));
 333    teardrop = is_bool(teardrop)&&teardrop? 45 : teardrop;
 334    chamfer = approx(chamfer,0) ? undef : chamfer;
 335    rounding = approx(rounding,0) ? undef : rounding;
 336    checks =
 337        assert(is_vector(size,3))
 338        assert(all_nonnegative(size), "All components of size= must be >=0")
 339        assert(is_undef(chamfer) || is_finite(chamfer),"chamfer must be a finite value")
 340        assert(is_undef(rounding) || is_finite(rounding),"rounding must be a finite value")
 341        assert(is_undef(rounding) || is_undef(chamfer), "Cannot specify nonzero value for both chamfer and rounding")
 342        assert(teardrop==false || (is_finite(teardrop) && teardrop>0 && teardrop<=90), "teardrop must be either false or an angle number between 0 and 90")
 343        assert(is_undef(p1) || is_vector(p1))
 344        assert(is_undef(p2) || is_vector(p2))
 345        assert(is_bool(trimcorners));
 346    if (!is_undef(p1)) {
 347        if (!is_undef(p2)) {
 348            translate(pointlist_bounds([p1,p2])[0]) {
 349                cuboid(size=v_abs(p2-p1), chamfer=chamfer, rounding=rounding, edges=edges, trimcorners=trimcorners, anchor=-[1,1,1]) children();
 350            }
 351        } else {
 352            translate(p1) {
 353                cuboid(size=size, chamfer=chamfer, rounding=rounding, edges=edges, trimcorners=trimcorners, anchor=-[1,1,1]) children();
 354            }
 355        }
 356    } else {
 357        rr = max(default(chamfer,0), default(rounding,0));
 358        if (rr>0) {
 359            minx = max(
 360                edges.y[0] + edges.y[1], edges.y[2] + edges.y[3],
 361                edges.z[0] + edges.z[1], edges.z[2] + edges.z[3],
 362                edges.y[0] + edges.z[1], edges.y[0] + edges.z[3],
 363                edges.y[1] + edges.z[0], edges.y[1] + edges.z[2],
 364                edges.y[2] + edges.z[1], edges.y[2] + edges.z[3],
 365                edges.y[3] + edges.z[0], edges.y[3] + edges.z[2]
 366            ) * rr;
 367            miny = max(
 368                edges.x[0] + edges.x[1], edges.x[2] + edges.x[3],
 369                edges.z[0] + edges.z[2], edges.z[1] + edges.z[3],
 370                edges.x[0] + edges.z[2], edges.x[0] + edges.z[3],
 371                edges.x[1] + edges.z[0], edges.x[1] + edges.z[1],
 372                edges.x[2] + edges.z[2], edges.x[2] + edges.z[3],
 373                edges.x[3] + edges.z[0], edges.x[3] + edges.z[1]
 374            ) * rr;
 375            minz = max(
 376                edges.x[0] + edges.x[2], edges.x[1] + edges.x[3],
 377                edges.y[0] + edges.y[2], edges.y[1] + edges.y[3],
 378                edges.x[0] + edges.y[2], edges.x[0] + edges.y[3],
 379                edges.x[1] + edges.y[2], edges.x[1] + edges.y[3],
 380                edges.x[2] + edges.y[0], edges.x[2] + edges.y[1],
 381                edges.x[3] + edges.y[0], edges.x[3] + edges.y[1]
 382            ) * rr;
 383            check =
 384                assert(minx <= size.x, "Rounding or chamfering too large for cuboid size in the X axis.")
 385                assert(miny <= size.y, "Rounding or chamfering too large for cuboid size in the Y axis.")
 386                assert(minz <= size.z, "Rounding or chamfering too large for cuboid size in the Z axis.")
 387            ;
 388        }
 389        majrots = [[0,90,0], [90,0,0], [0,0,0]];
 390        attachable(anchor,spin,orient, size=size) {
 391            if (is_finite(chamfer) && !approx(chamfer,0)) {
 392                if (edges == EDGES_ALL && trimcorners) {
 393                    if (chamfer<0) {
 394                        cube(size, center=true) {
 395                            attach(TOP,overlap=0) prismoid([size.x,size.y], [size.x-2*chamfer,size.y-2*chamfer], h=-chamfer, anchor=TOP);
 396                            attach(BOT,overlap=0) prismoid([size.x,size.y], [size.x-2*chamfer,size.y-2*chamfer], h=-chamfer, anchor=TOP);
 397                        }
 398                    } else {
 399                        isize = [for (v = size) max(0.001, v-2*chamfer)];
 400                        hull() {
 401                            cube([ size.x, isize.y, isize.z], center=true);
 402                            cube([isize.x,  size.y, isize.z], center=true);
 403                            cube([isize.x, isize.y,  size.z], center=true);
 404                        }
 405                    }
 406                } else if (chamfer<0) {
 407                    checks = assert(edges == EDGES_ALL || edges[2] == [0,0,0,0], "Cannot use negative chamfer with Z aligned edges.");
 408                    ach = abs(chamfer);
 409                    cube(size, center=true);
 410
 411                    // External-Chamfer mask edges
 412                    difference() {
 413                        union() {
 414                            for (i = [0:3], axis=[0:1]) {
 415                                if (edges[axis][i]>0) {
 416                                    vec = EDGE_OFFSETS[axis][i];
 417                                    translate(v_mul(vec/2, size+[ach,ach,-ach])) {
 418                                        rotate(majrots[axis]) {
 419                                            cube([ach, ach, size[axis]], center=true);
 420                                        }
 421                                    }
 422                                }
 423                            }
 424
 425                            // Add multi-edge corners.
 426                            if (trimcorners) {
 427                                for (za=[-1,1], ya=[-1,1], xa=[-1,1]) {
 428                                    ce = _corner_edges(edges, [xa,ya,za]);
 429                                    if (ce.x + ce.y > 1) {
 430                                        translate(v_mul([xa,ya,za]/2, size+[ach-0.01,ach-0.01,-ach])) {
 431                                            cube([ach+0.01,ach+0.01,ach], center=true);
 432                                        }
 433                                    }
 434                                }
 435                            }
 436                        }
 437
 438                        // Remove bevels from overhangs.
 439                        for (i = [0:3], axis=[0:1]) {
 440                            if (edges[axis][i]>0) {
 441                                vec = EDGE_OFFSETS[axis][i];
 442                                translate(v_mul(vec/2, size+[2*ach,2*ach,-2*ach])) {
 443                                    rotate(majrots[axis]) {
 444                                        zrot(45) cube([ach*sqrt(2), ach*sqrt(2), size[axis]+2.1*ach], center=true);
 445                                    }
 446                                }
 447                            }
 448                        }
 449                    }
 450                } else {
 451                    hull() {
 452                        corner_shape([-1,-1,-1]);
 453                        corner_shape([ 1,-1,-1]);
 454                        corner_shape([-1, 1,-1]);
 455                        corner_shape([ 1, 1,-1]);
 456                        corner_shape([-1,-1, 1]);
 457                        corner_shape([ 1,-1, 1]);
 458                        corner_shape([-1, 1, 1]);
 459                        corner_shape([ 1, 1, 1]);
 460                    }
 461                }
 462            } else if (is_finite(rounding) && !approx(rounding,0)) {
 463                sides = quantup(segs(rounding),4);
 464                if (edges == EDGES_ALL) {
 465                    if(rounding<0) {
 466                        cube(size, center=true);
 467                        zflip_copy() {
 468                            up(size.z/2) {
 469                                difference() {
 470                                    down(-rounding/2) cube([size.x-2*rounding, size.y-2*rounding, -rounding], center=true);
 471                                    down(-rounding) {
 472                                        ycopies(size.y-2*rounding) xcyl(l=size.x-3*rounding, r=-rounding);
 473                                        xcopies(size.x-2*rounding) ycyl(l=size.y-3*rounding, r=-rounding);
 474                                    }
 475                                }
 476                            }
 477                        }
 478                    } else {
 479                        isize = [for (v = size) max(0.001, v-2*rounding)];
 480                        minkowski() {
 481                            cube(isize, center=true);
 482                            if (trimcorners) {
 483                                tsphere(r=rounding, $fn=sides);
 484                            } else {
 485                                intersection() {
 486                                    xtcyl(r=rounding, l=rounding*2, $fn=sides);
 487                                    ytcyl(r=rounding, l=rounding*2, $fn=sides);
 488                                    cyl(r=rounding, h=rounding*2, $fn=sides);
 489                                }
 490                            }
 491                        }
 492                    }
 493                } else if (rounding<0) {
 494                    checks = assert(edges == EDGES_ALL || edges[2] == [0,0,0,0], "Cannot use negative rounding with Z aligned edges.");
 495                    ard = abs(rounding);
 496                    cube(size, center=true);
 497
 498                    // External-Rounding mask edges
 499                    difference() {
 500                        union() {
 501                            for (i = [0:3], axis=[0:1]) {
 502                                if (edges[axis][i]>0) {
 503                                    vec = EDGE_OFFSETS[axis][i];
 504                                    translate(v_mul(vec/2, size+[ard,ard,-ard]-[0.01,0.01,0])) {
 505                                        rotate(majrots[axis]) {
 506                                            cube([ard, ard, size[axis]], center=true);
 507                                        }
 508                                    }
 509                                }
 510                            }
 511
 512                            // Add multi-edge corners.
 513                            if (trimcorners) {
 514                                for (za=[-1,1], ya=[-1,1], xa=[-1,1]) {
 515                                    ce = _corner_edges(edges, [xa,ya,za]);
 516                                    if (ce.x + ce.y > 1) {
 517                                        translate(v_mul([xa,ya,za]/2, size+[ard-0.01,ard-0.01,-ard])) {
 518                                            cube([ard+0.01,ard+0.01,ard], center=true);
 519                                        }
 520                                    }
 521                                }
 522                            }
 523                        }
 524
 525                        // Remove roundings from overhangs.
 526                        for (i = [0:3], axis=[0:1]) {
 527                            if (edges[axis][i]>0) {
 528                                vec = EDGE_OFFSETS[axis][i];
 529                                translate(v_mul(vec/2, size+[2*ard,2*ard,-2*ard])) {
 530                                    rotate(majrots[axis]) {
 531                                        cyl(l=size[axis]+2.1*ard, r=ard);
 532                                    }
 533                                }
 534                            }
 535                        }
 536                    }
 537                } else {
 538                    hull() {
 539                        corner_shape([-1,-1,-1]);
 540                        corner_shape([ 1,-1,-1]);
 541                        corner_shape([-1, 1,-1]);
 542                        corner_shape([ 1, 1,-1]);
 543                        corner_shape([-1,-1, 1]);
 544                        corner_shape([ 1,-1, 1]);
 545                        corner_shape([-1, 1, 1]);
 546                        corner_shape([ 1, 1, 1]);
 547                    }
 548                }
 549            } else {
 550                cube(size=size, center=true);
 551            }
 552            children();
 553        }
 554    }
 555}
 556
 557
 558function cuboid(
 559    size=[1,1,1],
 560    p1, p2,
 561    chamfer,
 562    rounding,
 563    edges=EDGES_ALL,
 564    except_edges=[],
 565    trimcorners=true,
 566    anchor=CENTER,
 567    spin=0,
 568    orient=UP
 569) = no_function("cuboid");
 570
 571
 572
 573// Function&Module: prismoid()
 574// Synopsis: Creates a rectangular prismoid shape with optional roundovers and chamfering.
 575// SynTags: Geom, VNF
 576// Topics: Shapes (3D), Attachable, VNF Generators
 577// See Also: cuboid(), rounded_prism(), trapezoid(), edge_profile()
 578// Usage: 
 579//   prismoid(size1, size2, [h|l|height|length], [shift], [xang=], [yang=], ...) [ATTACHMENTS];
 580// Usage: Chamfered and/or Rounded Prismoids
 581//   prismoid(size1, size2, h|l|height|length, [chamfer=], [rounding=]...) [ATTACHMENTS];
 582//   prismoid(size1, size2, h|l|height|length, [chamfer1=], [chamfer2=], [rounding1=], [rounding2=], ...) [ATTACHMENTS];
 583// Usage: As Function
 584//   vnf = prismoid(...);
 585// Description:
 586//   Creates a rectangular prismoid shape with optional roundovers and chamfering.
 587//   You can only round or chamfer the vertical(ish) edges.  For those edges, you can
 588//   specify rounding and/or chamferring per-edge, and for top and bottom separately.
 589//   If you want to round the bottom or top edges see {{rounded_prism()}}.
 590//   .
 591//   Specification of the prismoid is similar to specification for {{trapezoid()}}.  You can specify the dimensions of the
 592//   bottom and top and its height to get a symmetric prismoid.  You can use the shift argument to shift the top face around.
 593//   You can also specify base angles either in the X direction, Y direction or both.  In order to avoid overspecification,
 594//   you may need to specify a parameter such as size2 as a list of two values, one of which is undef.  For example,
 595//   specifying `size2=[100,undef]` sets the size in the X direction but allows the size in the Y direction to be computed based on yang.
 596// Arguments:
 597//   size1 = [width, length] of the bottom end of the prism.
 598//   size2 = [width, length] of the top end of the prism.
 599//   h/l/height/length = Height of the prism.
 600//   shift = [X,Y] amount to shift the center of the top end with respect to the center of the bottom end.
 601//   ---
 602//   xang = base angle in the X direction.  Can be a scalar or list of two values, one of which may be undef
 603//   yang = base angle in the Y direction.  Can be a scalar or list of two values, one of which may be undef
 604//   rounding = The roundover radius for the vertical-ish edges of the prismoid.  If given as a list of four numbers, gives individual radii for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-]. Default: 0 (no rounding)
 605//   rounding1 = The roundover radius for the bottom of the vertical-ish edges of the prismoid.  If given as a list of four numbers, gives individual radii for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].
 606//   rounding2 = The roundover radius for the top of the vertical-ish edges of the prismoid.  If given as a list of four numbers, gives individual radii for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].
 607//   chamfer = The chamfer size for the vertical-ish edges of the prismoid.  If given as a list of four numbers, gives individual chamfers for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].  Default: 0 (no chamfer)
 608//   chamfer1 = The chamfer size for the bottom of the vertical-ish edges of the prismoid.  If given as a list of four numbers, gives individual chamfers for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].
 609//   chamfer2 = The chamfer size for the top of the vertical-ish edges of the prismoid.  If given as a list of four numbers, gives individual chamfers for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].
 610//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `BOTTOM`
 611//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
 612//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
 613//
 614// Example: Truncated Pyramid
 615//   prismoid(size1=[35,50], size2=[20,30], h=20);
 616// Example: Rectangular Pyramid
 617//   prismoid([40,40], [0,0], h=20);
 618// Example: Prism
 619//   prismoid(size1=[40,40], size2=[0,40], h=20);
 620// Example: Wedge
 621//   prismoid(size1=[60,35], size2=[30,0], h=30);
 622// Example: Truncated Tetrahedron
 623//   prismoid(size1=[10,40], size2=[40,10], h=40);
 624// Example: Inverted Truncated Pyramid
 625//   prismoid(size1=[15,5], size2=[30,20], h=20);
 626// Example: Right Prism
 627//   prismoid(size1=[30,60], size2=[0,60], shift=[-15,0], h=30);
 628// Example(FlatSpin,VPD=160,VPT=[0,0,10]): Shifting/Skewing
 629//   prismoid(size1=[50,30], size2=[20,20], h=20, shift=[15,5]);
 630// Example: Specifying bottom, height and angle
 631//   prismoid(size1=[100,75], h=30, xang=50, yang=70);
 632// Example: Specifying top, height and angle, with asymmetric angles
 633//   prismoid(size2=[100,75], h=30, xang=[50,60], yang=[70,40]);
 634// Example: Specifying top, bottom and angle for X and using that to define height.  Note that giving yang here would likely give a conflicting height calculation, which is not allowed.  
 635//   prismoid(size1=[100,75], size2=[75,35], xang=50);
 636// Example: The same as the previous example but we give a shift in Y.  Note that shift.x must be undef because you cannot give combine an angle with a shift, so a shift.x value would conflict with xang being defined.  
 637//   prismoid(size1=[100,75], size2=[75,35], xang=50, shift=[undef,20]);
 638// Example:  The X dimensions defined by the base length, angle and height; the Y dimensions defined by the top length, angle, and height. 
 639//   prismoid(size1=[100,undef], size2=[undef,75], h=30, xang=[20,90], yang=30);
 640// Example: Rounding
 641//   prismoid(100, 80, rounding=10, h=30);
 642// Example: Chamfers
 643//   prismoid(100, 80, chamfer=5, h=30);
 644// Example: Gradiant Rounding
 645//   prismoid(100, 80, rounding1=10, rounding2=0, h=30);
 646// Example: Per Corner Rounding
 647//   prismoid(100, 80, rounding=[0,5,10,15], h=30);
 648// Example: Per Corner Chamfer
 649//   prismoid(100, 80, chamfer=[0,5,10,15], h=30);
 650// Example: Mixing Chamfer and Rounding
 651//   prismoid(
 652//       100, 80, h=30,
 653//       chamfer=[0,5,0,10],
 654//       rounding=[5,0,10,0]
 655//   );
 656// Example: Really Mixing It Up
 657//   prismoid(
 658//       size1=[100,80], size2=[80,60], h=20,
 659//       chamfer1=[0,5,0,10], chamfer2=[5,0,10,0],
 660//       rounding1=[5,0,10,0], rounding2=[0,5,0,10]
 661//   );
 662// Example: How to Round a Top or Bottom Edge
 663//   diff()
 664//   prismoid([50,30], [30,20], shift=[3,6], h=15, rounding=[5,0,5,0]) {
 665//       edge_profile([TOP+RIGHT, BOT+FRONT], excess=10, convexity=20) {
 666//           mask2d_roundover(h=5,mask_angle=$edge_angle);
 667//       }
 668//   }
 669// Example(Spin,VPD=160,VPT=[0,0,10]): Standard Connectors
 670//   prismoid(size1=[50,30], size2=[20,20], h=20, shift=[15,5])
 671//       show_anchors();
 672
 673module prismoid(
 674    size1=undef, size2=undef, h, shift=[undef,undef],
 675    xang, yang,
 676    rounding=0, rounding1, rounding2,
 677    chamfer=0, chamfer1, chamfer2,
 678    l, height, length, center,
 679    anchor, spin=0, orient=UP
 680)
 681{
 682    vnf_s1_s2_shift = prismoid(
 683        size1=size1, size2=size2, h=h, shift=shift,
 684        xang=xang, yang=yang, 
 685        rounding=rounding, chamfer=chamfer, 
 686        rounding1=rounding1, rounding2=rounding2,
 687        chamfer1=chamfer1, chamfer2=chamfer2,
 688        l=l, height=height, length=length, anchor=BOT, _return_dim=true
 689    );
 690    anchor = get_anchor(anchor, center, BOT, BOT);
 691    attachable(anchor,spin,orient, size=vnf_s1_s2_shift[1], size2=vnf_s1_s2_shift[2], shift=vnf_s1_s2_shift[3]) {
 692        down(vnf_s1_s2_shift[1].z/2)
 693            vnf_polyhedron(vnf_s1_s2_shift[0], convexity=4);
 694        children();
 695    }
 696}
 697
 698function prismoid(
 699    size1, size2, h, shift=[0,0],
 700    rounding=0, rounding1, rounding2,
 701    chamfer=0, chamfer1, chamfer2,
 702    l, height, length, center,
 703    anchor=DOWN, spin=0, orient=UP, xang, yang,
 704    _return_dim=false
 705    
 706) =
 707    assert(is_undef(shift) || is_num(shift) || len(shift)==2, "shift must be a number or list of length 2")
 708    assert(is_undef(size1) || is_num(size1) || len(size1)==2, "size1 must be a number or list of length 2")
 709    assert(is_undef(size2) || is_num(size2) || len(size2)==2, "size2 must be a number or list of length 2")  
 710    let(
 711        xang = force_list(xang,2),
 712        yang = force_list(yang,2),
 713        yangOK = len(yang)==2 && (yang==[undef,undef] || (all_positive(yang) && yang[0]<180 && yang[1]<180)),
 714        xangOK = len(xang)==2 && (xang==[undef,undef] || (all_positive(xang) && xang[0]<180 && xang[1]<180)),
 715        size1=force_list(size1,2),
 716        size2=force_list(size2,2),
 717        h=first_defined([l,h,length,height]),
 718        shift = force_list(shift,2)
 719    )
 720    assert(xangOK, "prismoid angles must be scalar or 2-vector, strictly between 0 and 180")
 721    assert(yangOK, "prismoid angles must be scalar or 2-vector, strictly between 0 and 180")
 722    assert(xang==[undef,undef] || shift.x==undef, "Cannot specify xang and a shift.x value together")
 723    assert(yang==[undef,undef] || shift.y==undef, "Cannot specify yang and a shift.y value together")
 724    assert(all_positive([h]) || is_undef(h), "h must be a positive value")
 725    let(
 726        hx = _trapezoid_dims(h,size1.x,size2.x,shift.x,xang)[0],
 727        hy = _trapezoid_dims(h,size1.y,size2.y,shift.y,yang)[0]
 728    )
 729    assert(num_defined([hx,hy])>0, "Height not given and specification does not determine prismoid height")
 730    assert(hx==undef || hy==undef || approx(hx,hy),
 731           str("X and Y angle specifications give rise to conflicting height values ",hx," and ",hy))
 732    let(
 733        h = first_defined([hx,hy]),
 734        x_h_w1_w2_shift = _trapezoid_dims(h,size1.x,size2.x,shift.x,xang),
 735        y_h_w1_w2_shift = _trapezoid_dims(h,size1.y,size2.y,shift.y,yang)
 736    )
 737    let(
 738        s1 = [x_h_w1_w2_shift[1], y_h_w1_w2_shift[1]],
 739        s2 = [x_h_w1_w2_shift[2], y_h_w1_w2_shift[2]],
 740        shift = [x_h_w1_w2_shift[3], y_h_w1_w2_shift[3]]
 741    )
 742    assert(is_vector(s1,2), "Insufficient information to define prismoid")
 743    assert(is_vector(s2,2), "Insufficient information to define prismoid")
 744    assert(all_nonnegative(concat(s1,s2)),"Degenerate prismoid geometry")
 745    assert(s1.x+s2.x>0 && s1.y+s2.y>0, "Degenerate prismoid geometry")
 746    assert(is_num(rounding) || is_vector(rounding,4), "rounding must be a number or 4-vector")
 747    assert(is_undef(rounding1) || is_num(rounding1) || is_vector(rounding1,4), "rounding1 must be a number or 4-vector")
 748    assert(is_undef(rounding2) || is_num(rounding2) || is_vector(rounding2,4), "rounding2 must be a number or 4-vector")
 749    assert(is_num(chamfer) || is_vector(chamfer,4), "chamfer must be a number or 4-vector")
 750    assert(is_undef(chamfer1) || is_num(chamfer1) || is_vector(chamfer1,4), "chamfer1 must be a number or 4-vector")
 751    assert(is_undef(chamfer2) || is_num(chamfer2) || is_vector(chamfer2,4), "chamfer2 must be a number or 4-vector")
 752    let(
 753        chamfer1=force_list(default(chamfer1,chamfer),4),
 754        chamfer2=force_list(default(chamfer2,chamfer),4),
 755        rounding1=force_list(default(rounding1,rounding),4),
 756        rounding2=force_list(default(rounding2,rounding),4)
 757    )
 758    assert(all_nonnegative(chamfer1), "chamfer/chamfer1 must be non-negative")
 759    assert(all_nonnegative(chamfer2), "chamfer/chamfer2 must be non-negative")
 760    assert(all_nonnegative(rounding1), "rounding/rounding1 must be non-negative")
 761    assert(all_nonnegative(rounding2), "rounding/rounding2 must be non-negative")        
 762    assert(all_zero(v_mul(rounding1,chamfer1),0),
 763           "rounding1 and chamfer1 (possibly inherited from rounding and chamfer) cannot both be nonzero at the same corner")
 764    assert(all_zero(v_mul(rounding2,chamfer2),0),
 765           "rounding2 and chamfer2 (possibly inherited from rounding and chamfer) cannot both be nonzero at the same corner")
 766    let(
 767        rounding1 = default(rounding1, rounding),
 768        rounding2 = default(rounding2, rounding),
 769        chamfer1 = default(chamfer1, chamfer),
 770        chamfer2 = default(chamfer2, chamfer),
 771        anchor = get_anchor(anchor, center, BOT, BOT),
 772        path1 = rect(s1, rounding=rounding1, chamfer=chamfer1, anchor=CTR),
 773        path2 = rect(s2, rounding=rounding2, chamfer=chamfer2, anchor=CTR),
 774        points = [
 775                    each path3d(path1, -h/2),
 776                    each path3d(move(shift, path2), +h/2),
 777                 ],
 778        faces = hull(points),
 779        vnf = [points, faces]
 780    )
 781    _return_dim ? [reorient(anchor,spin,orient, size=[s1.x,s1.y,h], size2=s2, shift=shift, p=vnf),point3d(s1,h),s2,shift]
 782                : reorient(anchor,spin,orient, size=[s1.x,s1.y,h], size2=s2, shift=shift, p=vnf);
 783
 784
 785// Function&Module: octahedron()
 786// Synopsis: Creates an octahedron with axis-aligned points.
 787// SynTags: Geom, VNF
 788// Topics: Shapes (3D), Attachable, VNF Generators
 789// See Also: prismoid()
 790// Usage: As Module
 791//   octahedron(size, ...) [ATTACHMENTS];
 792// Usage: As Function
 793//   vnf = octahedron(size, ...);
 794// Description:
 795//   When called as a module, creates an octahedron with axis-aligned points.
 796//   When called as a function, creates a [VNF](vnf.scad) of an octahedron with axis-aligned points.
 797// Arguments:
 798//   size = Width of the octahedron, tip to tip.
 799//   ---
 800//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
 801//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
 802//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
 803// Example:
 804//   octahedron(size=40);
 805// Example: Anchors
 806//   octahedron(size=40) show_anchors();
 807
 808module octahedron(size=1, anchor=CENTER, spin=0, orient=UP) {
 809    vnf = octahedron(size=size);
 810    attachable(anchor,spin,orient, vnf=vnf, extent=true) {
 811        vnf_polyhedron(vnf, convexity=2);
 812        children();
 813    }
 814}
 815
 816function octahedron(size=1, anchor=CENTER, spin=0, orient=UP) =
 817    let(
 818        size = scalar_vec3(size),
 819        s = size/2,
 820        vnf = [
 821            [ [0,0,s.z], [s.x,0,0], [0,s.y,0], [-s.x,0,0], [0,-s.y,0], [0,0,-s.z] ],
 822            [ [0,2,1], [0,3,2], [0,4,3], [0,1,4], [5,1,2], [5,2,3], [5,3,4], [5,4,1] ]
 823        ]
 824    ) reorient(anchor,spin,orient, vnf=vnf, extent=true, p=vnf);
 825
 826
 827// Module: rect_tube()
 828// Synopsis: Creates a rectangular tube.
 829// SynTags: Geom
 830// Topics: Shapes (3D), Attachable, VNF Generators
 831// See Also: tube()
 832// Usage: Typical Rectangular Tubes
 833//   rect_tube(h, size, isize, [center], [shift]);
 834//   rect_tube(h, size, wall=, [center=]);
 835//   rect_tube(h, isize=, wall=, [center=]);
 836// Usage: Tapering Rectangular Tubes
 837//   rect_tube(h, size1=, size2=, wall=, ...);
 838//   rect_tube(h, isize1=, isize2=, wall=, ...);
 839//   rect_tube(h, size1=, size2=, isize1=, isize2=, ...);
 840// Usage: Chamfered
 841//   rect_tube(h, size, isize, chamfer=, ...);
 842//   rect_tube(h, size, isize, chamfer1=, chamfer2= ...);
 843//   rect_tube(h, size, isize, ichamfer=, ...);
 844//   rect_tube(h, size, isize, ichamfer1=, ichamfer2= ...);
 845//   rect_tube(h, size, isize, chamfer=, ichamfer=, ...);
 846// Usage: Rounded
 847//   rect_tube(h, size, isize, rounding=, ...);
 848//   rect_tube(h, size, isize, rounding1=, rounding2= ...);
 849//   rect_tube(h, size, isize, irounding=, ...);
 850//   rect_tube(h, size, isize, irounding1=, irounding2= ...);
 851//   rect_tube(h, size, isize, rounding=, irounding=, ...);
 852// Usage: Attaching Children
 853//   rect_tube(...) ATTACHMENTS;
 854//
 855// Description:
 856//   Creates a rectangular or prismoid tube with optional roundovers and/or chamfers.
 857//   You can only round or chamfer the vertical(ish) edges.  For those edges, you can
 858//   specify rounding and/or chamferring per-edge, and for top and bottom, inside and
 859//   outside  separately.
 860//   .
 861//   By default if you specify a chamfer or rounding then it applies as specified to the
 862//   outside, and an inside rounding is calculated that will maintain constant width
 863//   if your wall thickness is uniform.  If the wall thickness is not uniform, the default
 864//   inside rounding is calculated based on the smaller of the two wall thicknesses.
 865//   Note that the values of the more specific chamfers and roundings inherit from the
 866//   more general ones, so `rounding2` is determined from `rounding`.  The constant
 867//   width default will apply when the inner rounding and chamfer are both undef.
 868//   You can give an inner chamfer or rounding as a list with undef entries if you want to specify
 869//   some corner roundings and allow others to be computed.  
 870// Arguments:
 871//   h/l/height/length = The height or length of the rectangular tube.  Default: 1
 872//   size = The outer [X,Y] size of the rectangular tube.
 873//   isize = The inner [X,Y] size of the rectangular tube.
 874//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=UP`.
 875//   shift = [X,Y] amount to shift the center of the top end with respect to the center of the bottom end.
 876//   ---
 877//   wall = The thickness of the rectangular tube wall.
 878//   size1 = The [X,Y] size of the outside of the bottom of the rectangular tube.
 879//   size2 = The [X,Y] size of the outside of the top of the rectangular tube.
 880//   isize1 = The [X,Y] size of the inside of the bottom of the rectangular tube.
 881//   isize2 = The [X,Y] size of the inside of the top of the rectangular tube.
 882//   rounding = The roundover radius for the outside edges of the rectangular tube.
 883//   rounding1 = The roundover radius for the outside bottom corner of the rectangular tube.
 884//   rounding2 = The roundover radius for the outside top corner of the rectangular tube.
 885//   chamfer = The chamfer size for the outside edges of the rectangular tube.
 886//   chamfer1 = The chamfer size for the outside bottom corner of the rectangular tube.
 887//   chamfer2 = The chamfer size for the outside top corner of the rectangular tube.
 888//   irounding = The roundover radius for the inside edges of the rectangular tube. Default: Computed for uniform wall thickness (see above)
 889//   irounding1 = The roundover radius for the inside bottom corner of the rectangular tube.
 890//   irounding2 = The roundover radius for the inside top corner of the rectangular tube.
 891//   ichamfer = The chamfer size for the inside edges of the rectangular tube.  Default: Computed for uniform wall thickness (see above)
 892//   ichamfer1 = The chamfer size for the inside bottom corner of the rectangular tube.
 893//   ichamfer2 = The chamfer size for the inside top corner of the rectangular tube.
 894//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `BOTTOM`
 895//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
 896//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
 897// Examples:
 898//   rect_tube(size=50, wall=5, h=30);
 899//   rect_tube(size=[100,60], wall=5, h=30);
 900//   rect_tube(isize=[60,80], wall=5, h=30);
 901//   rect_tube(size=[100,60], isize=[90,50], h=30);
 902//   rect_tube(size1=[100,60], size2=[70,40], wall=5, h=30);
 903// Example:
 904//   rect_tube(
 905//       size1=[100,60], size2=[70,40],
 906//       isize1=[40,20], isize2=[65,35], h=15
 907//   );
 908// Example: With rounding
 909//   rect_tube(size=100, wall=5, rounding=10, h=30);
 910// Example: With rounding
 911//   rect_tube(size=100, wall=5, chamfer=10, h=30);
 912// Example: Outer Rounding Only
 913//   rect_tube(size=100, wall=5, rounding=10, irounding=0, h=30);
 914// Example: Outer Chamfer Only
 915//   rect_tube(size=100, wall=5, chamfer=5, ichamfer=0, h=30);
 916// Example: Outer Rounding, Inner Chamfer
 917//   rect_tube(size=100, wall=5, rounding=10, ichamfer=8, h=30);
 918// Example: Inner Rounding, Outer Chamfer
 919//   rect_tube(size=100, wall=5, chamfer=10, irounding=8, h=30);
 920// Example: Gradiant Rounding
 921//   rect_tube(
 922//       size1=100, size2=80, wall=5, h=30,
 923//       rounding1=10, rounding2=0,
 924//       irounding1=8, irounding2=0
 925//   );
 926// Example: Per Corner Rounding
 927//   rect_tube(
 928//       size=100, wall=10, h=30,
 929//       rounding=[0,5,10,15], irounding=0
 930//   );
 931// Example: Per Corner Chamfer
 932//   rect_tube(
 933//       size=100, wall=10, h=30,
 934//       chamfer=[0,5,10,15], ichamfer=0
 935//   );
 936// Example: Mixing Chamfer and Rounding
 937//   rect_tube(
 938//       size=100, wall=10, h=30,
 939//       chamfer=[0,10,0,20], 
 940//       rounding=[10,0,20,0]
 941//   );
 942// Example: Really Mixing It Up
 943//   rect_tube(
 944//       size1=[100,80], size2=[80,60],
 945//       isize1=[50,30], isize2=[70,50], h=20,
 946//       chamfer1=[0,5,0,10], ichamfer1=[0,3,0,8],
 947//       chamfer2=[5,0,10,0], ichamfer2=[3,0,8,0],
 948//       rounding1=[5,0,10,0], irounding1=[3,0,8,0],
 949//       rounding2=[0,5,0,10], irounding2=[0,3,0,8]
 950//   );
 951// Example: Some interiors chamfered, others with default rounding
 952//   rect_tube(
 953//       size=100, wall=10, h=30,
 954//       rounding=[0,10,20,30], ichamfer=[8,8,undef,undef]
 955//   );
 956
 957
 958
 959function _rect_tube_rounding(factor,ir,r,alternative,size,isize) =
 960    let(wall = min(size-isize)/2*factor)
 961    [for(i=[0:3])
 962      is_def(ir[i]) ? ir[i]
 963    : is_undef(alternative[i]) ? max(0,r[i]-wall)
 964    : 0
 965    ];
 966    
 967module rect_tube(
 968    h, size, isize, center, shift=[0,0],
 969    wall, size1, size2, isize1, isize2,
 970    rounding=0, rounding1, rounding2,
 971    irounding=undef, irounding1=undef, irounding2=undef,
 972    chamfer=0, chamfer1, chamfer2,
 973    ichamfer=undef, ichamfer1=undef, ichamfer2=undef,
 974    anchor, spin=0, orient=UP,
 975    l, length, height
 976) {
 977    h = one_defined([h,l,length,height],"h,l,length,height");
 978    checks =
 979        assert(is_num(h), "l or h argument required.")
 980        assert(is_vector(shift,2));
 981    s1 = is_num(size1)? [size1, size1] :
 982        is_vector(size1,2)? size1 :
 983        is_num(size)? [size, size] :
 984        is_vector(size,2)? size :
 985        undef;
 986    s2 = is_num(size2)? [size2, size2] :
 987        is_vector(size2,2)? size2 :
 988        is_num(size)? [size, size] :
 989        is_vector(size,2)? size :
 990        undef;
 991    is1 = is_num(isize1)? [isize1, isize1] :
 992        is_vector(isize1,2)? isize1 :
 993        is_num(isize)? [isize, isize] :
 994        is_vector(isize,2)? isize :
 995        undef;
 996    is2 = is_num(isize2)? [isize2, isize2] :
 997        is_vector(isize2,2)? isize2 :
 998        is_num(isize)? [isize, isize] :
 999        is_vector(isize,2)? isize :
1000        undef;
1001    size1 = is_def(s1)? s1 :
1002        (is_def(wall) && is_def(is1))? (is1+2*[wall,wall]) :
1003        undef;
1004    size2 = is_def(s2)? s2 :
1005        (is_def(wall) && is_def(is2))? (is2+2*[wall,wall]) :
1006        undef;
1007    isize1 = is_def(is1)? is1 :
1008        (is_def(wall) && is_def(s1))? (s1-2*[wall,wall]) :
1009        undef;
1010    isize2 = is_def(is2)? is2 :
1011        (is_def(wall) && is_def(s2))? (s2-2*[wall,wall]) :
1012        undef;
1013    checks2 =
1014        assert(wall==undef || is_num(wall))
1015        assert(size1!=undef, "Bad size/size1 argument.")
1016        assert(size2!=undef, "Bad size/size2 argument.")
1017        assert(isize1!=undef, "Bad isize/isize1 argument.")
1018        assert(isize2!=undef, "Bad isize/isize2 argument.")
1019        assert(isize1.x < size1.x, "Inner size is larger than outer size.")
1020        assert(isize1.y < size1.y, "Inner size is larger than outer size.")
1021        assert(isize2.x < size2.x, "Inner size is larger than outer size.")
1022        assert(isize2.y < size2.y, "Inner size is larger than outer size.")
1023        assert(is_num(rounding) || is_vector(rounding,4), "rounding must be a number or 4-vector")
1024        assert(is_undef(rounding1) || is_num(rounding1) || is_vector(rounding1,4), "rounding1 must be a number or 4-vector")
1025        assert(is_undef(rounding2) || is_num(rounding2) || is_vector(rounding2,4), "rounding2 must be a number or 4-vector")
1026        assert(is_num(chamfer) || is_vector(chamfer,4), "chamfer must be a number or 4-vector")
1027        assert(is_undef(chamfer1) || is_num(chamfer1) || is_vector(chamfer1,4), "chamfer1 must be a number or 4-vector")
1028        assert(is_undef(chamfer2) || is_num(chamfer2) || is_vector(chamfer2,4), "chamfer2 must be a number or 4-vector")
1029        assert(is_undef(irounding) || is_num(irounding) || (is_list(irounding) && len(irounding)==4), "irounding must be a number or 4-vector")
1030        assert(is_undef(irounding1) || is_num(irounding1) || (is_list(irounding1) && len(irounding1)==4), "irounding1 must be a number or 4-vector")
1031        assert(is_undef(irounding2) || is_num(irounding2) || (is_list(irounding2) && len(irounding2)==4), "irounding2 must be a number or 4-vector")      
1032        assert(is_undef(ichamfer) || is_num(ichamfer) || (is_list(ichamfer) && len(ichamfer)==4), "ichamfer must be a number or 4-vector")
1033        assert(is_undef(ichamfer1) || is_num(ichamfer1) || (is_list(ichamfer1) && len(ichamfer1)==4), "ichamfer1 must be a number or 4-vector")
1034        assert(is_undef(ichamfer2) || is_num(ichamfer2) || (is_list(ichamfer2) && len(ichamfer2)==4), "ichamfer2 must be a number or 4-vector");
1035    chamfer1=force_list(default(chamfer1,chamfer),4);
1036    chamfer2=force_list(default(chamfer2,chamfer),4);
1037    rounding1=force_list(default(rounding1,rounding),4);
1038    rounding2=force_list(default(rounding2,rounding),4);
1039    checks3 =
1040        assert(all_nonnegative(chamfer1), "chamfer/chamfer1 must be non-negative")
1041        assert(all_nonnegative(chamfer2), "chamfer/chamfer2 must be non-negative")
1042        assert(all_nonnegative(rounding1), "rounding/rounding1 must be non-negative")
1043        assert(all_nonnegative(rounding2), "rounding/rounding2 must be non-negative")        
1044        assert(all_zero(v_mul(rounding1,chamfer1),0), "rounding1 and chamfer1 (possibly inherited from rounding and chamfer) cannot both be nonzero at the same corner")
1045        assert(all_zero(v_mul(rounding2,chamfer2),0), "rounding2 and chamfer2 (possibly inherited from rounding and chamfer) cannot both be nonzero at the same corner");
1046    irounding1_temp = force_list(default(irounding1,irounding),4);
1047    irounding2_temp = force_list(default(irounding2,irounding),4);    
1048    ichamfer1_temp = force_list(default(ichamfer1,ichamfer),4);
1049    ichamfer2_temp = force_list(default(ichamfer2,ichamfer),4);
1050    checksignr1 = [for(entry=irounding1_temp) if (is_def(entry) && entry<0) 1]==[];
1051    checksignr2 = [for(entry=irounding2_temp) if (is_def(entry) && entry<0) 1]==[];    
1052    checksignc1 = [for(entry=ichamfer1_temp) if (is_def(entry) && entry<0) 1]==[];
1053    checksignc2 = [for(entry=ichamfer2_temp) if (is_def(entry) && entry<0) 1]==[];
1054    checkconflict1 = [for(i=[0:3]) if (is_def(irounding1_temp[i]) && is_def(ichamfer1_temp[i]) && irounding1_temp[i]!=0 && ichamfer1_temp[i]!=0) 1]==[];
1055    checkconflict2 = [for(i=[0:3]) if (is_def(irounding2_temp[i]) && is_def(ichamfer2_temp[i]) && irounding2_temp[i]!=0 && ichamfer2_temp[i]!=0) 1]==[];
1056    checks4 =
1057        assert(checksignr1, "irounding/irounding1 must be non-negative")
1058        assert(checksignr2, "irounding/irounding2 must be non-negative")
1059        assert(checksignc1, "ichamfer/ichamfer1 must be non-negative")
1060        assert(checksignc2, "ichamfer/ichamfer2 must be non-negative")
1061        assert(checkconflict1, "irounding1 and ichamfer1 (possibly inherited from irounding and ichamfer) cannot both be nonzero at the swame corner")
1062        assert(checkconflict2, "irounding2 and ichamfer2 (possibly inherited from irounding and ichamfer) cannot both be nonzero at the swame corner");
1063    irounding1 = _rect_tube_rounding(1,irounding1_temp, rounding1, ichamfer1_temp, size1, isize1);
1064    irounding2 = _rect_tube_rounding(1,irounding2_temp, rounding2, ichamfer2_temp, size2, isize2);
1065    ichamfer1 = _rect_tube_rounding(1/sqrt(2),ichamfer1_temp, chamfer1, irounding1_temp, size1, isize1);
1066    ichamfer2 = _rect_tube_rounding(1/sqrt(2),ichamfer2_temp, chamfer2, irounding2_temp, size2, isize2);
1067    anchor = get_anchor(anchor, center, BOT, BOT);
1068    attachable(anchor,spin,orient, size=[each size1, h], size2=size2, shift=shift) {
1069        down(h/2) {
1070            difference() {
1071                prismoid(
1072                    size1, size2, h=h, shift=shift,
1073                    rounding1=rounding1, rounding2=rounding2,
1074                    chamfer1=chamfer1, chamfer2=chamfer2,
1075                    anchor=BOT
1076                );
1077                down(0.01) prismoid(
1078                    isize1, isize2, h=h+0.02, shift=shift,
1079                    rounding1=irounding1, rounding2=irounding2,
1080                    chamfer1=ichamfer1, chamfer2=ichamfer2,
1081                    anchor=BOT
1082                );
1083            }
1084        }
1085        children();
1086    }
1087}
1088
1089function rect_tube(
1090    h, size, isize, center, shift=[0,0],
1091    wall, size1, size2, isize1, isize2,
1092    rounding=0, rounding1, rounding2,
1093    irounding, irounding1, irounding2,
1094    chamfer=0, chamfer1, chamfer2,
1095    ichamfer, ichamfer1, ichamfer2,
1096    anchor, spin=0, orient=UP,
1097    l, length, height
1098) = no_function("rect_tube");
1099
1100
1101// Function&Module: wedge()
1102// Synopsis: Creates a 3d triangular wedge.
1103// SynTags: Geom, VNF
1104// Topics: Shapes (3D), Attachable, VNF Generators
1105// See also: prismoid(), rounded_prism(), pie_slice()
1106// Usage: As Module
1107//   wedge(size, [center], ...) [ATTACHMENTS];
1108// Usage: As Function
1109//   vnf = wedge(size, [center], ...);
1110//
1111// Description:
1112//   When called as a module, creates a 3D triangular wedge with the hypotenuse in the X+Z+ quadrant.
1113//   When called as a function, creates a VNF for a 3D triangular wedge with the hypotenuse in the X+Z+ quadrant.
1114//
1115// Arguments:
1116//   size = [width, thickness, height]
1117//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=UP`.
1118//   ---
1119//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `FRONT+LEFT+BOTTOM`
1120//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
1121//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
1122//
1123// Named Anchors:
1124//   "hypot" = Center of angled wedge face, perpendicular to that face.
1125//   "hypot_left" = Left side of angled wedge face, bisecting the angle between the left side and angled faces.
1126//   "hypot_right" = Right side of angled wedge face, bisecting the angle between the right side and angled faces.
1127//
1128// Example: Centered
1129//   wedge([20, 40, 15], center=true);
1130// Example: *Non*-Centered
1131//   wedge([20, 40, 15]);
1132// Example: Standard Anchors
1133//   wedge([40, 80, 30], center=true)
1134//       show_anchors(custom=false);
1135//   color([0.5,0.5,0.5,0.1])
1136//       cube([40, 80, 30], center=true);
1137// Example: Named Anchors
1138//   wedge([40, 80, 30], center=true)
1139//       show_anchors(std=false);
1140
1141module wedge(size=[1, 1, 1], center, anchor, spin=0, orient=UP)
1142{
1143    size = scalar_vec3(size);
1144    anchor = get_anchor(anchor, center, -[1,1,1], -[1,1,1]);
1145    vnf = wedge(size, anchor="origin");
1146    anchors = [
1147        named_anchor("hypot", CTR, unit([0,size.z,size.y],UP)),
1148        named_anchor("hypot_left", [-size.x/2,0,0], unit(unit([0,size.z,size.y],UP)+LEFT)),
1149        named_anchor("hypot_right", [size.x/2,0,0], unit(unit([0,size.z,size.y],UP)+RIGHT)),
1150    ];
1151    attachable(anchor,spin,orient, size=size, anchors=anchors) {
1152        if (size.z > 0) {
1153            vnf_polyhedron(vnf);
1154        }
1155        children();
1156    }
1157}
1158
1159
1160function wedge(size=[1,1,1], center, anchor, spin=0, orient=UP) =
1161    let(
1162        size = scalar_vec3(size),
1163        anchor = get_anchor(anchor, center, -[1,1,1], -[1,1,1]),
1164        pts = [
1165            [ 1,1,-1], [ 1,-1,-1], [ 1,-1,1],
1166            [-1,1,-1], [-1,-1,-1], [-1,-1,1],
1167        ],
1168        faces = [
1169            [0,1,2], [3,5,4], [0,3,1], [1,3,4],
1170            [1,4,2], [2,4,5], [2,5,3], [0,2,3],
1171        ],
1172        vnf = [scale(size/2,p=pts), faces],
1173        anchors = [
1174            named_anchor("hypot", CTR, unit([0,size.z,size.y],UP)),
1175            named_anchor("hypot_left", [-size.x/2,0,0], unit(unit([0,size.z,size.y],UP)+LEFT)),
1176            named_anchor("hypot_right", [size.x/2,0,0], unit(unit([0,size.z,size.y],UP)+RIGHT)),
1177        ]
1178    )
1179    reorient(anchor,spin,orient, size=size, anchors=anchors, p=vnf);
1180
1181
1182// Section: Cylinders
1183
1184
1185// Function&Module: cylinder()
1186// Synopsis: Creates an attachable cylinder.
1187// SynTags: Geom, VNF, Ext
1188// Topics: Shapes (3D), Attachable, VNF Generators
1189// See Also: cyl()
1190// Usage: As Module (as in native OpenSCAD)
1191//   cylinder(h, r=/d=, [center=]);
1192//   cylinder(h, r1/d1=, r2/d2=, [center=]);
1193// Usage: With BOSL2 anchoring and attachment extensions
1194//   cylinder(h, r=/d=, [center=], [anchor=], [spin=], [orient=]) [ATTACHMENTS];
1195//   cylinder(h, r1/d1=, r2/d2=, [center=], [anchor=], [spin=], [orient=]) [ATTACHMENTS];
1196// Usage: As Function (BOSL2 extension)
1197//   vnf = cylinder(h, r=/d=, ...);
1198//   vnf = cylinder(h, r1/d1=, r2/d2=, ...);
1199// Description:
1200//   Creates a 3D cylinder or conic object.
1201//   This modules extends the built-in `cylinder()` module by adding support for attachment and by adding a function version.   
1202//   When called as a function, returns a [VNF](vnf.scad) for a cylinder.  
1203// Arguments:
1204//   h = The height of the cylinder.
1205//   r1 = The bottom radius of the cylinder.  (Before orientation.)
1206//   r2 = The top radius of the cylinder.  (Before orientation.)
1207//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=BOTTOM`.  Default: false
1208//   ---
1209//   d1 = The bottom diameter of the cylinder.  (Before orientation.)
1210//   d2 = The top diameter of the cylinder.  (Before orientation.)
1211//   r = The radius of the cylinder.
1212//   d = The diameter of the cylinder.
1213//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
1214//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
1215//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
1216// Example: By Radius
1217//   xdistribute(30) {
1218//       cylinder(h=40, r=10);
1219//       cylinder(h=40, r1=10, r2=5);
1220//   }
1221// Example: By Diameter
1222//   xdistribute(30) {
1223//       cylinder(h=40, d=25);
1224//       cylinder(h=40, d1=25, d2=10);
1225//   }
1226// Example(Med): Anchoring
1227//   cylinder(h=40, r1=10, r2=5, anchor=BOTTOM+FRONT);
1228// Example(Med): Spin
1229//   cylinder(h=40, r1=10, r2=5, anchor=BOTTOM+FRONT, spin=45);
1230// Example(Med): Orient
1231//   cylinder(h=40, r1=10, r2=5, anchor=BOTTOM+FRONT, spin=45, orient=FWD);
1232// Example(Big): Standard Connectors
1233//   xdistribute(40) {
1234//       cylinder(h=30, d=25) show_anchors();
1235//       cylinder(h=30, d1=25, d2=10) show_anchors();
1236//   }
1237
1238module cylinder(h, r1, r2, center, r, d, d1, d2, anchor, spin=0, orient=UP)
1239{
1240    anchor = get_anchor(anchor, center, BOTTOM, BOTTOM);
1241    r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1);
1242    r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1);
1243    h = default(h,1);
1244    attachable(anchor,spin,orient, r1=r1, r2=r2, l=h) {
1245        _cylinder(h=h, r1=r1, r2=r2, center=true);
1246        children();
1247    }
1248}
1249
1250function cylinder(h, r1, r2, center, r, d, d1, d2, anchor, spin=0, orient=UP) =
1251    let(
1252        anchor = get_anchor(anchor, center, BOTTOM, BOTTOM),
1253        r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1),
1254        r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1),
1255        l = default(h,1),
1256        sides = segs(max(r1,r2)),
1257        verts = [
1258            for (i=[0:1:sides-1]) let(a=360*(1-i/sides)) [r1*cos(a),r1*sin(a),-l/2],
1259            for (i=[0:1:sides-1]) let(a=360*(1-i/sides)) [r2*cos(a),r2*sin(a), l/2],
1260        ],
1261        faces = [
1262            [for (i=[0:1:sides-1]) sides-1-i],
1263            for (i=[0:1:sides-1]) [i, ((i+1)%sides)+sides, i+sides],
1264            for (i=[0:1:sides-1]) [i, (i+1)%sides, ((i+1)%sides)+sides],
1265            [for (i=[0:1:sides-1]) sides+i]
1266        ]
1267    ) [reorient(anchor,spin,orient, l=l, r1=r1, r2=r2, p=verts), faces];
1268
1269
1270
1271// Function&Module: cyl()
1272// Synopsis: Creates an attachable cylinder with roundovers and chamfering.
1273// SynTags: Geom, VNF
1274// Topics: Cylinders, Textures, Rounding, Chamfers
1275// See Also: texture(), rotate_sweep(), cylinder()
1276// Usage: Normal Cylinders
1277//   cyl(l|h|length|height, r, [center], [circum=], [realign=]) [ATTACHMENTS];
1278//   cyl(l|h|length|height, d=, ...) [ATTACHMENTS];
1279//   cyl(l|h|length|height, r1=, r2=, ...) [ATTACHMENTS];
1280//   cyl(l|h|length|height, d1=, d2=, ...) [ATTACHMENTS];
1281//
1282// Usage: Chamferred Cylinders
1283//   cyl(l|h|length|height, r|d, chamfer=, [chamfang=], [from_end=], ...);
1284//   cyl(l|h|length|height, r|d, chamfer1=, [chamfang1=], [from_end=], ...);
1285//   cyl(l|h|length|height, r|d, chamfer2=, [chamfang2=], [from_end=], ...);
1286//   cyl(l|h|length|height, r|d, chamfer1=, chamfer2=, [chamfang1=], [chamfang2=], [from_end=], ...);
1287//
1288// Usage: Rounded End Cylinders
1289//   cyl(l|h|length|height, r|d, rounding=, ...);
1290//   cyl(l|h|length|height, r|d, rounding1=, ...);
1291//   cyl(l|h|length|height, r|d, rounding2=, ...);
1292//   cyl(l|h|length|height, r|d, rounding1=, rounding2=, ...);
1293//
1294// Usage: Textured Cylinders
1295//   cyl(l|h|length|height, r|d, texture=, [tex_size=]|[tex_reps=], [tex_depth=], [tex_rot=], [tex_samples=], [style=], [tex_taper=], [tex_inset=], ...);
1296//   cyl(l|h|length|height, r1=, r2=, texture=, [tex_size=]|[tex_reps=], [tex_depth=], [tex_rot=], [tex_samples=], [style=], [tex_taper=], [tex_inset=], ...);
1297//   cyl(l|h|length|height, d1=, d2=, texture=, [tex_size=]|[tex_reps=], [tex_depth=], [tex_rot=], [tex_samples=], [style=], [tex_taper=], [tex_inset=], ...);
1298//
1299// Usage: Caled as a function to get a VNF
1300//   vnf = cyl(...);
1301//
1302// Description:
1303//   Creates cylinders in various anchorings and orientations, with optional rounding, chamfers, or textures.
1304//   You can use `h` and `l` interchangably, and all variants allow specifying size by either `r`|`d`,
1305//   or `r1`|`d1` and `r2`|`d2`.  Note: the chamfers and rounding cannot be cumulatively longer than
1306//   the cylinder or cone's sloped side.  The more specific parameters like chamfer1 or rounding2 override the more
1307//   general ones like chamfer or rounding, so if you specify `rounding=3, chamfer2=3` you will get a chamfer at the top and
1308//   rounding at the bottom.
1309// Figure(2D,Big,NoAxes,VPR = [0, 0, 0], VPT = [0,0,0], VPD = 82): Chamfers on cones can be tricky.  This figure shows chamfers of the same size and same angle, A=30 degrees.  Note that the angle is measured on the inside, and produces a quite different looking chamfer at the top and bottom of the cone.  Straight black arrows mark the size of the chamfers, which may not even appear the same size visually.  When you do not give an angle, the triangle that is cut off will be isoceles, like the triangle at the top, with two equal angles.
1310//  color("lightgray")
1311//  projection()
1312//      cyl(r2=10, r1=20, l=20,chamfang=30, chamfer=0,orient=BACK);
1313//  projection()
1314//      cyl(r2=10, r1=20, l=20,chamfang=30, chamfer=8,orient=BACK);
1315//  color("black"){
1316//      fwd(9.6)right(20-4.8)text("A",size=1.3);
1317//      fwd(-8.4)right(10-4.9)text("A",size=1.3);
1318//      right(20-8)fwd(10.5)stroke([[0,0],[8,0]], endcaps="arrow2",width=.15);
1319//      right(10-8)fwd(-10.5)stroke([[0,0],[8,0]], endcaps="arrow2",width=.15);
1320//      stroke(arc(cp=[2,10], angle=[0,-30], n=20, r=5), width=.18, endcaps="arrow2");
1321//      stroke(arc(cp=[12,-10], angle=[0,30], n=20, r=5), width=.18, endcaps="arrow2");
1322//  }
1323// Figure(2D,Big,NoAxes,VPR = [0, 0, 0], VPT = [0,0,0], VPD = 82): The cone in this example is narrow but has the same slope.  With negative chamfers, the angle A=30 degrees is on the outside.  The chamfers are again quite different looking.  As before, the default will feature two congruent angles, and in this case it happens at the bottom of the cone but not the top.  The straight arrows again show the size of the chamfer.
1324//  r1=10-7.5;r2=20-7.5;
1325//  color("lightgray")
1326//  projection()
1327//      cyl(r2=r1, r1=r2, l=20,chamfang=30, chamfer=-8,orient=BACK);
1328//  projection()
1329//      cyl(r2=r1, r1=r2, l=20,chamfang=30, chamfer=0,orient=BACK);
1330//  color("black"){
1331//      fwd(9.7)right(r2+3.8)text("A",size=1.3);
1332//      fwd(-8.5)right(r1+3.7)text("A",size=1.3);
1333//      right(r2)fwd(10.5)stroke([[0,0],[8,0]], endcaps="arrow2",width=.15);
1334//      right(r1)fwd(-10.5)stroke([[0,0],[8,0]], endcaps="arrow2",width=.15);
1335//      stroke(arc(cp=[r1+8,10], angle=[180,180+30], n=20, r=5), width=.18, endcaps="arrow2");
1336//      stroke(arc(cp=[r2+8,-10], angle=[180-30,180], n=20, r=5), width=.18, endcaps="arrow2");
1337//  }
1338// Arguments:
1339//   l / h / length / height = Length of cylinder along oriented axis.  Default: 1
1340//   r = Radius of cylinder.  Default: 1
1341//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=DOWN`.
1342//   ---
1343//   r1 = Radius of the negative (X-, Y-, Z-) end of cylinder.
1344//   r2 = Radius of the positive (X+, Y+, Z+) end of cylinder.
1345//   d = Diameter of cylinder.
1346//   d1 = Diameter of the negative (X-, Y-, Z-) end of cylinder.
1347//   d2 = Diameter of the positive (X+, Y+, Z+) end of cylinder.
1348//   circum = If true, cylinder should circumscribe the circle of the given size.  Otherwise inscribes.  Default: `false`
1349//   shift = [X,Y] amount to shift the center of the top end with respect to the center of the bottom end.
1350//   chamfer = The size of the chamfers on the ends of the cylinder.  (Also see: `from_end=`)  Default: none.
1351//   chamfer1 = The size of the chamfer on the bottom end of the cylinder.  (Also see: `from_end1=`)  Default: none.
1352//   chamfer2 = The size of the chamfer on the top end of the cylinder.  (Also see: `from_end2=`)  Default: none.
1353//   chamfang = The angle in degrees of the chamfers away from the ends of the cylinder.  Default: Chamfer angle is halfway between the endcap and cone face.
1354//   chamfang1 = The angle in degrees of the bottom chamfer away from the bottom end of the cylinder.  Default: Chamfer angle is halfway between the endcap and cone face.
1355//   chamfang2 = The angle in degrees of the top chamfer away from the top end of the cylinder.  Default: Chamfer angle is halfway between the endcap and cone face.
1356//   from_end = If true, chamfer is measured along the conic face from the ends of the cylinder, instead of inset from the edge.  Default: `false`.
1357//   from_end1 = If true, chamfer on the bottom end of the cylinder is measured along the conic face from the end of the cylinder, instead of inset from the edge.  Default: `false`.
1358//   from_end2 = If true, chamfer on the top end of the cylinder is measured along the conic face from the end of the cylinder, instead of inset from the edge.  Default: `false`.
1359//   rounding = The radius of the rounding on the ends of the cylinder.  Default: none.
1360//   rounding1 = The radius of the rounding on the bottom end of the cylinder.
1361//   rounding2 = The radius of the rounding on the top end of the cylinder.
1362//   realign = If true, rotate the cylinder by half the angle of one face.
1363//   teardrop = If given as a number, rounding around the bottom edge of the cylinder won't exceed this many degrees from vertical.  If true, the limit angle is 45 degrees.  Default: `false`
1364//   texture = A texture name string, or a rectangular array of scalar height values (0.0 to 1.0), or a VNF tile that defines the texture to apply to vertical surfaces.  See {{texture()}} for what named textures are supported.
1365//   tex_size = An optional 2D target size for the textures.  Actual texture sizes will be scaled somewhat to evenly fit the available surface. Default: `[5,5]`
1366//   tex_reps = If given instead of tex_size, a 2-vector giving the number of texture tile repetitions in the horizontal and vertical directions.
1367//   tex_inset = If numeric, lowers the texture into the surface by the specified proportion, e.g. 0.5 would lower it half way into the surface.  If `true`, insets by exactly its full depth.  Default: `false`
1368//   tex_rot = Rotate texture by specified angle, which must be a multiple of 90 degrees.  Default: 0
1369//   tex_depth = Specify texture depth; if negative, invert the texture.  Default: 1.  
1370//   tex_samples = Minimum number of "bend points" to have in VNF texture tiles.  Default: 8
1371//   tex_taper = If given as a number, tapers the texture height to zero over the first and last given percentage of the path.  If given as a lookup table with indices between 0 and 100, uses the percentage lookup table to ramp the texture heights.  Default: `undef` (no taper)
1372//   style = {{vnf_vertex_array()}} style used to triangulate heightfield textures.  Default: "min_edge"
1373//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
1374//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
1375//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
1376//
1377//
1378// Example: By Radius
1379//   xdistribute(30) {
1380//       cyl(l=40, r=10);
1381//       cyl(l=40, r1=10, r2=5);
1382//   }
1383//
1384// Example: By Diameter
1385//   xdistribute(30) {
1386//       cyl(l=40, d=25);
1387//       cyl(l=40, d1=25, d2=10);
1388//   }
1389//
1390// Example: Chamferring
1391//   xdistribute(60) {
1392//       // Shown Left to right.
1393//       cyl(l=40, d=40, chamfer=7);  // Default chamfang=45
1394//       cyl(l=40, d=40, chamfer=7, chamfang=30, from_end=false);
1395//       cyl(l=40, d=40, chamfer=7, chamfang=30, from_end=true);
1396//   }
1397//
1398// Example: Rounding
1399//   cyl(l=40, d=40, rounding=10);
1400//
1401// Example(VPD=175;VPR=[90,0,0]): Teardrop Bottom Rounding
1402//   cyl(l=40, d=40, rounding=10, teardrop=true);
1403//
1404// Example: Heterogenous Chamfers and Rounding
1405//   ydistribute(80) {
1406//       // Shown Front to Back.
1407//       cyl(l=40, d=40, rounding1=15, orient=UP);
1408//       cyl(l=40, d=40, chamfer2=5, orient=UP);
1409//       cyl(l=40, d=40, chamfer1=12, rounding2=10, orient=UP);
1410//   }
1411//
1412// Example: Putting it all together
1413//   cyl(
1414//       l=20, d1=25, d2=15,
1415//       chamfer1=5, chamfang1=60,
1416//       from_end=true, rounding2=5
1417//   );
1418//
1419// Example: External Chamfers
1420//   cyl(l=50, r=30, chamfer=-5, chamfang=30, $fa=1, $fs=1);
1421//
1422// Example: External Roundings
1423//   cyl(l=50, r=30, rounding1=-5, rounding2=5, $fa=1, $fs=1);
1424//
1425// Example(Med): Standard Connectors
1426//   xdistribute(40) {
1427//       cyl(l=30, d=25) show_anchors();
1428//       cyl(l=30, d1=25, d2=10) show_anchors();
1429//   }
1430//
1431// Example: Texturing with heightfield diamonds
1432//   cyl(h=40, r=20, texture="diamonds", tex_size=[5,5]);
1433//
1434// Example: Texturing with heightfield pyramids
1435//   cyl(h=40, r1=20, r2=15,
1436//       texture="pyramids", tex_size=[5,5],
1437//       style="convex");
1438//
1439// Example: Texturing with heightfield truncated pyramids
1440//   cyl(h=40, r1=20, r2=15, chamfer=5,
1441//       texture="trunc_pyramids",
1442//       tex_size=[5,5], style="convex");
1443//
1444// Example: Texturing with VNF tile "dots"
1445//   cyl(h=40, r1=20, r2=15, rounding=9,
1446//       texture="dots", tex_size=[5,5],
1447//       tex_samples=6);
1448//
1449// Example: Texturing with VNF tile "bricks_vnf"
1450//   cyl(h=50, r1=25, r2=20, shift=[0,10], rounding1=-10,
1451//       texture="bricks_vnf", tex_size=[10,10],
1452//       tex_depth=0.5, style="concave");
1453//
1454// Example: No Texture Taper
1455//   cyl(d1=25, d2=20, h=30, rounding=5,
1456//       texture="trunc_ribs", tex_size=[5,1]);
1457//
1458// Example: Taper Texure at Extreme Ends
1459//   cyl(d1=25, d2=20, h=30, rounding=5,
1460//       texture="trunc_ribs", tex_taper=0,
1461//       tex_size=[5,1]);
1462//
1463// Example: Taper Texture over First and Last 10%
1464//   cyl(d1=25, d2=20, h=30, rounding=5,
1465//       texture="trunc_ribs", tex_taper=10,
1466//       tex_size=[5,1]);
1467//
1468// Example: Making a Clay Pattern Roller
1469//   tex = [
1470//       [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,],
1471//       [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,],
1472//       [1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,],
1473//       [1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,],
1474//       [0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,],
1475//       [0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,],
1476//       [0,1,1,0,0,1,1,0,0,1,1,1,1,1,1,0,],
1477//       [0,1,1,0,0,1,1,0,0,1,1,1,1,1,1,0,],
1478//       [0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,],
1479//       [0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,],
1480//       [0,1,1,0,0,1,1,1,1,1,1,0,0,1,1,0,],
1481//       [0,1,1,0,0,1,1,1,1,1,1,0,0,1,1,0,],
1482//       [0,1,1,0,0,0,0,0,0,0,0,0,0,1,1,0,],
1483//       [0,1,1,0,0,0,0,0,0,0,0,0,0,1,1,0,],
1484//       [0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,],
1485//       [0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,],
1486//       [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,],
1487//   ];
1488//   diff()
1489//   cyl(d=20*10/PI, h=10, chamfer=0,
1490//       texture=tex, tex_reps=[20,1], tex_depth=-1,
1491//       tex_taper=undef, style="concave") {
1492//           attach([TOP,BOT]) {
1493//               cyl(d1=20*10/PI, d2=30, h=5, anchor=BOT)
1494//                   attach(TOP) {
1495//                       tag("remove") zscale(0.5) up(3) sphere(d=15);
1496//                   }
1497//           }
1498//   }
1499
1500function cyl(
1501    h, r, center,
1502    l, r1, r2,
1503    d, d1, d2,
1504    length, height,
1505    chamfer, chamfer1, chamfer2,
1506    chamfang, chamfang1, chamfang2,
1507    rounding, rounding1, rounding2,
1508    circum=false, realign=false, shift=[0,0],
1509    teardrop=false,
1510    from_end, from_end1, from_end2,
1511    texture, tex_size=[5,5], tex_reps, tex_counts,
1512    tex_inset=false, tex_rot=0,
1513    tex_scale, tex_depth, tex_samples, length, height, 
1514    tex_taper, style, tex_style,
1515    anchor, spin=0, orient=UP
1516) =
1517    assert(num_defined([style,tex_style])<2, "In cyl() the 'tex_style' parameters has been replaced by 'style'.  You cannot give both.")
1518    assert(num_defined([tex_reps,tex_counts])<2, "In cyl() the 'tex_counts' parameters has been replaced by 'tex_reps'.  You cannot give both.")    
1519    assert(num_defined([tex_scale,tex_depth])<2, "In linear_sweep() the 'tex_scale' parameter has been replaced by 'tex_depth'.  You cannot give both.")
1520    let(
1521        style = is_def(tex_style)? echo("In cyl() the 'tex_style' parameter is deprecated and has been replaced by 'style'")tex_style
1522              : default(style,"min_edge"),
1523        tex_reps = is_def(tex_counts)? echo("In cyl() the 'tex_counts' parameter is deprecated and has been replaced by 'tex_reps'")tex_counts
1524                 : tex_reps,
1525        tex_depth = is_def(tex_scale)? echo("In rotate_sweep() the 'tex_scale' parameter is deprecated and has been replaced by 'tex_depth'")tex_scale
1526                  : default(tex_depth,1),
1527        l = one_defined([l, h, length, height],"l,h,length,height",dflt=1),
1528        _r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1),
1529        _r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1),
1530        sides = segs(max(_r1,_r2)),
1531        sc = circum? 1/cos(180/sides) : 1,
1532        r1 = _r1 * sc,
1533        r2 = _r2 * sc,
1534        phi = atan2(l, r2-r1),
1535        anchor = get_anchor(anchor,center,BOT,CENTER)
1536    )
1537    assert(is_finite(l), "l/h/length/height must be a finite number.")
1538    assert(is_finite(r1), "r/r1/d/d1 must be a finite number.")
1539    assert(is_finite(r2), "r2 or d2 must be a finite number.")
1540    assert(is_vector(shift,2), "shift must be a 2D vector.")
1541    let(
1542        vnf = !any_defined([chamfer, chamfer1, chamfer2, rounding, rounding1, rounding2, texture])
1543          ? cylinder(h=l, r1=r1, r2=r2, center=true, $fn=sides)
1544          : let(
1545                vang = atan2(r1-r2,l),
1546                _chamf1 = first_defined([chamfer1, if (is_undef(rounding1)) chamfer, 0]),
1547                _chamf2 = first_defined([chamfer2, if (is_undef(rounding2)) chamfer, 0]),
1548                _fromend1 = first_defined([from_end1, from_end, false]),
1549                _fromend2 = first_defined([from_end2, from_end, false]),
1550                chang1 = first_defined([chamfang1, chamfang, 45+sign(_chamf1)*vang/2]),
1551                chang2 = first_defined([chamfang2, chamfang, 45-sign(_chamf2)*vang/2]),
1552                round1 = first_defined([rounding1, if (is_undef(chamfer1)) rounding, 0]),
1553                round2 = first_defined([rounding2, if (is_undef(chamfer2)) rounding, 0]),
1554                checks1 =
1555                    assert(is_finite(_chamf1), "chamfer1 must be a finite number if given.")
1556                    assert(is_finite(_chamf2), "chamfer2 must be a finite number if given.")
1557                    assert(is_finite(chang1) && chang1>0, "chamfang1 must be a positive number if given.")
1558                    assert(is_finite(chang2) && chang2>0, "chamfang2 must be a positive number if given.")
1559                    assert(chang1<90+sign(_chamf1)*vang, "chamfang1 must be smaller than the cone face angle")
1560                    assert(chang2<90-sign(_chamf2)*vang, "chamfang2 must be smaller than the cone face angle")
1561                    assert(num_defined([chamfer1,rounding1])<2, "cannot define both chamfer1 and rounding1")
1562                    assert(num_defined([chamfer2,rounding2])<2, "cannot define both chamfer2 and rounding2")
1563                    assert(num_defined([chamfer,rounding])<2, "cannot define both chamfer and rounding")                                
1564                    undef,
1565                chamf1r = !_chamf1? 0
1566                        : !_fromend1? _chamf1
1567                        : law_of_sines(a=_chamf1, A=chang1, B=180-chang1-(90-sign(_chamf2)*vang)),
1568                chamf2r = !_chamf2? 0
1569                        : !_fromend2? _chamf2
1570                        : law_of_sines(a=_chamf2, A=chang2, B=180-chang2-(90+sign(_chamf2)*vang)),
1571                chamf1l = !_chamf1? 0
1572                        : _fromend1? abs(_chamf1)
1573                        : abs(law_of_sines(a=_chamf1, A=180-chang1-(90-sign(_chamf1)*vang), B=chang1)),
1574                chamf2l = !_chamf2? 0
1575                        : _fromend2? abs(_chamf2)
1576                        : abs(law_of_sines(a=_chamf2, A=180-chang2-(90+sign(_chamf2)*vang), B=chang2)),
1577                facelen = adj_ang_to_hyp(l, abs(vang)),
1578
1579                cp1 = [r1,-l/2],
1580                cp2 = [r2,+l/2],
1581                roundlen1 = round1 >= 0 ? round1/tan(45-vang/2)
1582                                        : round1/tan(45+vang/2),
1583                roundlen2 = round2 >=0 ? round2/tan(45+vang/2)
1584                                       : round2/tan(45-vang/2),
1585                dy1 = abs(_chamf1 ? chamf1l : round1 ? roundlen1 : 0), 
1586                dy2 = abs(_chamf2 ? chamf2l : round2 ? roundlen2 : 0),
1587
1588                td_ang = teardrop == true? 45 :
1589                    teardrop == false? 90 :
1590                    assert(is_finite(teardrop))
1591                    assert(teardrop>=0 && teardrop<=90)
1592                    teardrop,
1593
1594                checks2 =
1595                    assert(is_finite(round1), "rounding1 must be a number if given.")
1596                    assert(is_finite(round2), "rounding2 must be a number if given.")
1597                    assert(chamf1r <= r1, "chamfer1 is larger than the r1 radius of the cylinder.")
1598                    assert(chamf2r <= r2, "chamfer2 is larger than the r2 radius of the cylinder.")
1599                    assert(roundlen1 <= r1, "size of rounding1 is larger than the r1 radius of the cylinder.")
1600                    assert(roundlen2 <= r2, "size of rounding2 is larger than the r2 radius of the cylinder.")
1601                    assert(dy1+dy2 <= facelen, "Chamfers/roundings don't fit on the cylinder/cone.  They exceed the length of the cylinder/cone face.")
1602                    undef,
1603                path = [
1604                    if (texture==undef) [0,-l/2],
1605
1606                    if (!approx(chamf1r,0))
1607                        each [
1608                            [r1, -l/2] + polar_to_xy(chamf1r,180),
1609                            [r1, -l/2] + polar_to_xy(chamf1l,90+vang),
1610                        ]
1611                    else if (!approx(round1,0) && td_ang < 90)
1612                        each _teardrop_corner(r=round1, corner=[[max(0,r1-2*roundlen1),-l/2],[r1,-l/2],[r2,l/2]], ang=td_ang)
1613                    else if (!approx(round1,0) && td_ang >= 90)
1614                        each arc(r=abs(round1), corner=[[max(0,r1-2*roundlen1),-l/2],[r1,-l/2],[r2,l/2]])
1615                    else [r1,-l/2],
1616
1617                    if (is_finite(chamf2r) && !approx(chamf2r,0))
1618                        each [
1619                            [r2, l/2] + polar_to_xy(chamf2l,270+vang),
1620                            [r2, l/2] + polar_to_xy(chamf2r,180),
1621                        ]
1622                    else if (is_finite(round2) && !approx(round2,0))
1623                        each arc(r=abs(round2), corner=[[r1,-l/2],[r2,l/2],[max(0,r2-2*roundlen2),l/2]])
1624                    else [r2,l/2],
1625
1626                    if (texture==undef) [0,l/2],
1627                ]
1628            ) rotate_sweep(path,
1629                texture=texture, tex_reps=tex_reps, tex_size=tex_size,
1630                tex_inset=tex_inset, tex_rot=tex_rot,
1631                tex_depth=tex_depth, tex_samples=tex_samples,
1632                tex_taper=tex_taper, style=style, closed=false,
1633                _tex_inhibit_y_slicing=true
1634            ),
1635        skmat = down(l/2) *
1636            skew(sxz=shift.x/l, syz=shift.y/l) *
1637            up(l/2) *
1638            zrot(realign? 180/sides : 0),
1639        ovnf = apply(skmat, vnf)
1640    )
1641    reorient(anchor,spin,orient, r1=r1, r2=r2, l=l, shift=shift, p=ovnf);
1642
1643
1644function _teardrop_corner(r, corner, ang=45) =
1645    let(
1646        check = assert(len(corner)==3)
1647            assert(is_finite(r))
1648            assert(is_finite(ang)),
1649        cp = circle_2tangents(abs(r), corner)[0],
1650        path1 = arc(r=abs(r), corner=corner),
1651        path2 = [
1652            for (p = select(path1,0,-2))
1653                if (abs(modang(v_theta(p-cp)-90)) <= 180-ang) p,
1654            last(path1)
1655        ],
1656        path = [
1657            line_intersection([corner[0],corner[1]],[path2[0],path2[0]+polar_to_xy(1,-90-ang*sign(r))]),
1658            each path2
1659        ]
1660    ) path;
1661
1662
1663module cyl(
1664    h, r, center,
1665    l, r1, r2,
1666    d, d1, d2,
1667    chamfer, chamfer1, chamfer2,
1668    chamfang, chamfang1, chamfang2,
1669    rounding, rounding1, rounding2,
1670    circum=false, realign=false, shift=[0,0],
1671    teardrop=false,
1672    from_end, from_end1, from_end2,
1673    texture, tex_size=[5,5], tex_reps, tex_counts,
1674    tex_inset=false, tex_rot=0,
1675    tex_scale, tex_depth, tex_samples, length, height, 
1676    tex_taper, style, tex_style,
1677    anchor, spin=0, orient=UP
1678) {
1679    dummy=
1680      assert(num_defined([style,tex_style])<2, "In cyl() the 'tex_style' parameters has been replaced by 'style'.  You cannot give both.")
1681      assert(num_defined([tex_reps,tex_counts])<2, "In cyl() the 'tex_counts' parameters has been replaced by 'tex_reps'.  You cannot give both.")
1682      assert(num_defined([tex_scale,tex_depth])<2, "In cyl() the 'tex_scale' parameter has been replaced by 'tex_depth'.  You cannot give both.");
1683    style = is_def(tex_style)? echo("In cyl the 'tex_style()' parameters is deprecated and has been replaced by 'style'")tex_style
1684          : default(style,"min_edge");
1685    tex_reps = is_def(tex_counts)? echo("In cyl() the 'tex_counts' parameter is deprecated and has been replaced by 'tex_reps'")tex_counts
1686             : tex_reps;
1687    tex_depth = is_def(tex_scale)? echo("In rotate_sweep() the 'tex_scale' parameter is deprecated and has been replaced by 'tex_depth'")tex_scale
1688              : default(tex_depth,1);
1689    l = one_defined([l, h, length, height],"l,h,length,height",dflt=1);
1690    _r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1);
1691    _r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1);
1692    sides = segs(max(_r1,_r2));
1693    sc = circum? 1/cos(180/sides) : 1;
1694    r1 = _r1 * sc;
1695    r2 = _r2 * sc;
1696    phi = atan2(l, r2-r1);
1697    anchor = get_anchor(anchor,center,BOT,CENTER);
1698    skmat = down(l/2) * skew(sxz=shift.x/l, syz=shift.y/l) * up(l/2);
1699    attachable(anchor,spin,orient, r1=r1, r2=r2, l=l, shift=shift) {
1700        multmatrix(skmat)
1701        zrot(realign? 180/sides : 0) {
1702            if (!any_defined([chamfer, chamfer1, chamfer2, rounding, rounding1, rounding2, texture])) {
1703                cylinder(h=l, r1=r1, r2=r2, center=true, $fn=sides);
1704            } else {
1705                vnf = cyl(
1706                    l=l, r1=r1, r2=r2, center=true, 
1707                    chamfer=chamfer, chamfer1=chamfer1, chamfer2=chamfer2,
1708                    chamfang=chamfang, chamfang1=chamfang1, chamfang2=chamfang2,
1709                    rounding=rounding, rounding1=rounding1, rounding2=rounding2,
1710                    from_end=from_end, from_end1=from_end1, from_end2=from_end2,
1711                    teardrop=teardrop,
1712                    texture=texture, tex_size=tex_size,
1713                    tex_reps=tex_reps, tex_depth=tex_depth,
1714                    tex_inset=tex_inset, tex_rot=tex_rot,
1715                    style=style, tex_taper=tex_taper,
1716                    tex_samples=tex_samples
1717                );
1718                vnf_polyhedron(vnf, convexity=texture!=undef? 2 : 10);
1719            }
1720        }
1721        children();
1722    }
1723}
1724
1725
1726
1727// Module: xcyl()
1728// Synopsis: creates a cylinder oriented along the X axis.
1729// SynTags: Geom
1730// Topics: Cylinders, Textures, Rounding, Chamfers
1731// See Also: texture(), rotate_sweep(), cyl()
1732// Description:
1733//   Creates an attachable cylinder with roundovers and chamfering oriented along the X axis.
1734//
1735// Usage: Typical
1736//   xcyl(l|h|length|height, r|d=, [anchor=], ...) [ATTACHMENTS];
1737//   xcyl(l|h|length|height, r1=|d1=, r2=|d2=, [anchor=], ...) [ATTACHMENTS];
1738//
1739// Arguments:
1740//   l / h / length / height = Length of cylinder along oriented axis. Default: 1
1741//   r = Radius of cylinder.  Default: 1
1742//   ---
1743//   r1 = Optional radius of left (X-) end of cylinder.
1744//   r2 = Optional radius of right (X+) end of cylinder.
1745//   d = Optional diameter of cylinder. (use instead of `r`)
1746//   d1 = Optional diameter of left (X-) end of cylinder.
1747//   d2 = Optional diameter of right (X+) end of cylinder.
1748//   circum = If true, cylinder should circumscribe the circle of the given size.  Otherwise inscribes.  Default: `false`
1749//   chamfer = The size of the chamfers on the ends of the cylinder.  Default: none.
1750//   chamfer1 = The size of the chamfer on the left end of the cylinder.  Default: none.
1751//   chamfer2 = The size of the chamfer on the right end of the cylinder.  Default: none.
1752//   chamfang = The angle in degrees of the chamfers on the ends of the cylinder.
1753//   chamfang1 = The angle in degrees of the chamfer on the left end of the cylinder.
1754//   chamfang2 = The angle in degrees of the chamfer on the right end of the cylinder.
1755//   from_end = If true, chamfer is measured from the end of the cylinder, instead of inset from the edge.  Default: `false`.
1756//   rounding = The radius of the rounding on the ends of the cylinder.  Default: none.
1757//   rounding1 = The radius of the rounding on the left end of the cylinder.
1758//   rounding2 = The radius of the rounding on the right end of the cylinder.
1759//   realign = If true, rotate the cylinder by half the angle of one face.
1760//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
1761//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
1762//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
1763//
1764// Example: By Radius
1765//   ydistribute(50) {
1766//       xcyl(l=35, r=10);
1767//       xcyl(l=35, r1=15, r2=5);
1768//   }
1769//
1770// Example: By Diameter
1771//   ydistribute(50) {
1772//       xcyl(l=35, d=20);
1773//       xcyl(l=35, d1=30, d2=10);
1774//   }
1775
1776function xcyl(
1777    h, r, d, r1, r2, d1, d2, l, 
1778    chamfer, chamfer1, chamfer2,
1779    chamfang, chamfang1, chamfang2,
1780    rounding, rounding1, rounding2,
1781    circum=false, realign=false, from_end=false, length, height,
1782    anchor=CENTER, spin=0, orient=UP
1783) = no_function("xcyl");
1784
1785module xcyl(
1786    h, r, d, r1, r2, d1, d2, l, 
1787    chamfer, chamfer1, chamfer2,
1788    chamfang, chamfang1, chamfang2,
1789    rounding, rounding1, rounding2,
1790    circum=false, realign=false, from_end=false, length, height,
1791    anchor=CENTER, spin=0, orient=UP
1792) {
1793    r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1);
1794    r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1);
1795    l = one_defined([l,h,length,height],"l,h,length,height",1);
1796    attachable(anchor,spin,orient, r1=r1, r2=r2, l=l, axis=RIGHT) {
1797        cyl(
1798            l=l, r1=r1, r2=r2,
1799            chamfer=chamfer, chamfer1=chamfer1, chamfer2=chamfer2,
1800            chamfang=chamfang, chamfang1=chamfang1, chamfang2=chamfang2,
1801            rounding=rounding, rounding1=rounding1, rounding2=rounding2,
1802            circum=circum, realign=realign, from_end=from_end,
1803            anchor=CENTER, orient=RIGHT
1804        );
1805        children();
1806    }
1807}
1808
1809
1810// Module: ycyl()
1811// Synopsis: Creates a cylinder oriented along the y axis.
1812// SynTags: Geom
1813// Topics: Cylinders, Textures, Rounding, Chamfers
1814// See Also: texture(), rotate_sweep(), cyl()
1815// Description:
1816//   Creates an attachable cylinder with roundovers and chamfering oriented along the y axis.
1817//
1818// Usage: Typical
1819//   ycyl(l|h|length|height, r|d=, [anchor=], ...) [ATTACHMENTS];
1820//   ycyl(l|h|length|height, r1=|d1=, r2=|d2=, [anchor=], ...) [ATTACHMENTS];
1821//
1822// Arguments:
1823//   l / h / length / height = Length of cylinder along oriented axis. (Default: `1.0`)
1824//   r = Radius of cylinder.
1825//   ---
1826//   r1 = Radius of front (Y-) end of cone.
1827//   r2 = Radius of back (Y+) end of one.
1828//   d = Diameter of cylinder.
1829//   d1 = Diameter of front (Y-) end of one.
1830//   d2 = Diameter of back (Y+) end of one.
1831//   circum = If true, cylinder should circumscribe the circle of the given size.  Otherwise inscribes.  Default: `false`
1832//   chamfer = The size of the chamfers on the ends of the cylinder.  Default: none.
1833//   chamfer1 = The size of the chamfer on the front end of the cylinder.  Default: none.
1834//   chamfer2 = The size of the chamfer on the back end of the cylinder.  Default: none.
1835//   chamfang = The angle in degrees of the chamfers on the ends of the cylinder.
1836//   chamfang1 = The angle in degrees of the chamfer on the front end of the cylinder.
1837//   chamfang2 = The angle in degrees of the chamfer on the back end of the cylinder.
1838//   from_end = If true, chamfer is measured from the end of the cylinder, instead of inset from the edge.  Default: `false`.
1839//   rounding = The radius of the rounding on the ends of the cylinder.  Default: none.
1840//   rounding1 = The radius of the rounding on the front end of the cylinder.
1841//   rounding2 = The radius of the rounding on the back end of the cylinder.
1842//   realign = If true, rotate the cylinder by half the angle of one face.
1843//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
1844//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
1845//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
1846//
1847// Example: By Radius
1848//   xdistribute(50) {
1849//       ycyl(l=35, r=10);
1850//       ycyl(l=35, r1=15, r2=5);
1851//   }
1852//
1853// Example: By Diameter
1854//   xdistribute(50) {
1855//       ycyl(l=35, d=20);
1856//       ycyl(l=35, d1=30, d2=10);
1857//   }
1858
1859function ycyl(
1860    h, r, d, r1, r2, d1, d2, l,
1861    chamfer, chamfer1, chamfer2,
1862    chamfang, chamfang1, chamfang2,
1863    rounding, rounding1, rounding2,
1864    circum=false, realign=false, from_end=false,height,length,
1865    anchor=CENTER, spin=0, orient=UP
1866) = no_function("ycyl");
1867
1868
1869module ycyl(
1870    h, r, d, r1, r2, d1, d2, l,
1871    chamfer, chamfer1, chamfer2,
1872    chamfang, chamfang1, chamfang2,
1873    rounding, rounding1, rounding2,
1874    circum=false, realign=false, from_end=false,height,length,
1875    anchor=CENTER, spin=0, orient=UP
1876) {
1877    r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1);
1878    r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1);
1879    l = one_defined([l,h,length,height],"l,h,length,height",1);
1880    attachable(anchor,spin,orient, r1=r1, r2=r2, l=l, axis=BACK) {
1881        cyl(
1882            l=l, r1=r1, r2=r2,
1883            chamfer=chamfer, chamfer1=chamfer1, chamfer2=chamfer2,
1884            chamfang=chamfang, chamfang1=chamfang1, chamfang2=chamfang2,
1885            rounding=rounding, rounding1=rounding1, rounding2=rounding2,
1886            circum=circum, realign=realign, from_end=from_end,
1887            anchor=CENTER, orient=BACK
1888        );
1889        children();
1890    }
1891}
1892
1893
1894
1895// Module: zcyl()
1896// Synopsis: Creates a cylinder oriented along the Z axis.
1897// SynTags: Geom
1898// Topics: Cylinders, Textures, Rounding, Chamfers
1899// See Also: texture(), rotate_sweep(), cyl()
1900// Description:
1901//   Creates an attachable cylinder with roundovers and chamfering oriented along the Z axis.
1902//
1903// Usage: Typical
1904//   zcyl(l|h|length|height, r|d=, [anchor=],...) [ATTACHMENTS];
1905//   zcyl(l|h|length|height, r1=|d1=, r2=|d2=, [anchor=],...);
1906//
1907// Arguments:
1908//   l / h / length / height = Length of cylinder along oriented axis. (Default: 1.0)
1909//   r = Radius of cylinder.
1910//   ---
1911//   r1 = Radius of front (Y-) end of cone.
1912//   r2 = Radius of back (Y+) end of one.
1913//   d = Diameter of cylinder.
1914//   d1 = Diameter of front (Y-) end of one.
1915//   d2 = Diameter of back (Y+) end of one.
1916//   circum = If true, cylinder should circumscribe the circle of the given size.  Otherwise inscribes.  Default: `false`
1917//   chamfer = The size of the chamfers on the ends of the cylinder.  Default: none.
1918//   chamfer1 = The size of the chamfer on the bottom end of the cylinder.  Default: none.
1919//   chamfer2 = The size of the chamfer on the top end of the cylinder.  Default: none.
1920//   chamfang = The angle in degrees of the chamfers on the ends of the cylinder.
1921//   chamfang1 = The angle in degrees of the chamfer on the bottom end of the cylinder.
1922//   chamfang2 = The angle in degrees of the chamfer on the top end of the cylinder.
1923//   from_end = If true, chamfer is measured from the end of the cylinder, instead of inset from the edge.  Default: `false`.
1924//   rounding = The radius of the rounding on the ends of the cylinder.  Default: none.
1925//   rounding1 = The radius of the rounding on the bottom end of the cylinder.
1926//   rounding2 = The radius of the rounding on the top end of the cylinder.
1927//   realign = If true, rotate the cylinder by half the angle of one face.
1928//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
1929//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
1930//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
1931//
1932// Example: By Radius
1933//   xdistribute(50) {
1934//       zcyl(l=35, r=10);
1935//       zcyl(l=35, r1=15, r2=5);
1936//   }
1937//
1938// Example: By Diameter
1939//   xdistribute(50) {
1940//       zcyl(l=35, d=20);
1941//       zcyl(l=35, d1=30, d2=10);
1942//   }
1943
1944function zcyl(
1945    h, r, d, r1, r2, d1, d2, l,
1946    chamfer, chamfer1, chamfer2,
1947    chamfang, chamfang1, chamfang2,
1948    rounding, rounding1, rounding2,
1949    circum=false, realign=false, from_end=false, length, height,
1950    anchor=CENTER, spin=0, orient=UP
1951) = no_function("zcyl");
1952
1953module zcyl(
1954    h, r, d, r1, r2, d1, d2, l,
1955    chamfer, chamfer1, chamfer2,
1956    chamfang, chamfang1, chamfang2,
1957    rounding, rounding1, rounding2,
1958    circum=false, realign=false, from_end=false, length, height,
1959    anchor=CENTER, spin=0, orient=UP
1960) {
1961    r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1);
1962    r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1);
1963    l = one_defined([l,h,length,height],"l,h,length,height",1);
1964    attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) {
1965        cyl(
1966            l=l, r1=r1, r2=r2,
1967            chamfer=chamfer, chamfer1=chamfer1, chamfer2=chamfer2,
1968            chamfang=chamfang, chamfang1=chamfang1, chamfang2=chamfang2,
1969            rounding=rounding, rounding1=rounding1, rounding2=rounding2,
1970            circum=circum, realign=realign, from_end=from_end,
1971            anchor=CENTER
1972        );
1973        children();
1974    }
1975}
1976
1977
1978
1979// Module: tube()
1980// Synopsis: Creates a cylindrical or conical tube.
1981// SynTags: Geom
1982// Topics: Shapes (3D), Attachable, VNF Generators
1983// See Also: rect_tube()
1984// Description:
1985//   Makes a hollow tube that can be cylindrical or conical by specifying inner and outer dimensions or by giving one dimension and
1986//   wall thickness. 
1987// Usage: Basic cylindrical tube, specifying inner and outer radius or diameter
1988//   tube(h|l, or, ir, [center], [realign=], [anchor=], [spin=],[orient=]) [ATTACHMENTS];
1989//   tube(h|l, od=, id=, ...)  [ATTACHMENTS];
1990// Usage: Specify wall thickness
1991//   tube(h|l, or|od=|ir=|id=, wall=, ...) [ATTACHMENTS];
1992// Usage: Conical tubes
1993//   tube(h|l, ir1=|id1=, ir2=|id2=, or1=|od1=, or2=|od2=, ...) [ATTACHMENTS];
1994//   tube(h|l, or1=|od1=, or2=|od2=, wall=, ...) [ATTACHMENTS];
1995// Arguments:
1996//   h / l = height of tube. Default: 1
1997//   or = Outer radius of tube. Default: 1
1998//   ir = Inner radius of tube.
1999//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=DOWN`.
2000//   ---
2001//   od = Outer diameter of tube.
2002//   id = Inner diameter of tube.
2003//   wall = horizontal thickness of tube wall. Default 1
2004//   or1 = Outer radius of bottom of tube.  Default: value of r)
2005//   or2 = Outer radius of top of tube.  Default: value of r)
2006//   od1 = Outer diameter of bottom of tube.
2007//   od2 = Outer diameter of top of tube.
2008//   ir1 = Inner radius of bottom of tube.
2009//   ir2 = Inner radius of top of tube.
2010//   id1 = Inner diameter of bottom of tube.
2011//   id2 = Inner diameter of top of tube.
2012//   realign = If true, rotate the tube by half the angle of one face.
2013//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
2014//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
2015//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
2016//
2017// Example: These all Produce the Same Tube
2018//   tube(h=30, or=40, wall=5);
2019//   tube(h=30, ir=35, wall=5);
2020//   tube(h=30, or=40, ir=35);
2021//   tube(h=30, od=80, id=70);
2022// Example: These all Produce the Same Conical Tube
2023//   tube(h=30, or1=40, or2=25, wall=5);
2024//   tube(h=30, ir1=35, or2=20, wall=5);
2025//   tube(h=30, or1=40, or2=25, ir1=35, ir2=20);
2026// Example: Circular Wedge
2027//   tube(h=30, or1=40, or2=30, ir1=20, ir2=30);
2028// Example: Standard Connectors
2029//   tube(h=30, or=40, wall=5) show_anchors();
2030
2031function tube(
2032     h, or, ir, center,
2033    od, id, wall,
2034    or1, or2, od1, od2,
2035    ir1, ir2, id1, id2,
2036    realign=false, l, length, height,
2037    anchor, spin=0, orient=UP
2038) = no_function("tube");
2039
2040module tube(
2041    h, or, ir, center,
2042    od, id, wall,
2043    or1, or2, od1, od2,
2044    ir1, ir2, id1, id2,
2045    realign=false, l, length, height,
2046    anchor, spin=0, orient=UP
2047) {
2048    h = one_defined([h,l,height,length],"h,l,height,length",dflt=1);
2049    orr1 = get_radius(r1=or1, r=or, d1=od1, d=od, dflt=undef);
2050    orr2 = get_radius(r1=or2, r=or, d1=od2, d=od, dflt=undef);
2051    irr1 = get_radius(r1=ir1, r=ir, d1=id1, d=id, dflt=undef);
2052    irr2 = get_radius(r1=ir2, r=ir, d1=id2, d=id, dflt=undef);
2053    wall = default(wall, 1);
2054    r1 = default(orr1, u_add(irr1,wall));
2055    r2 = default(orr2, u_add(irr2,wall));
2056    ir1 = default(irr1, u_sub(orr1,wall));
2057    ir2 = default(irr2, u_sub(orr2,wall));
2058    checks =
2059        assert(all_defined([r1, r2, ir1, ir2]), "Must specify two of inner radius/diam, outer radius/diam, and wall width.")
2060        assert(ir1 <= r1, "Inner radius is larger than outer radius.")
2061        assert(ir2 <= r2, "Inner radius is larger than outer radius.");
2062    sides = segs(max(r1,r2));
2063    anchor = get_anchor(anchor, center, BOT, CENTER);
2064    attachable(anchor,spin,orient, r1=r1, r2=r2, l=h) {
2065        zrot(realign? 180/sides : 0) {
2066            difference() {
2067                cyl(h=h, r1=r1, r2=r2, $fn=sides) children();
2068                cyl(h=h+0.05, r1=ir1, r2=ir2);
2069            }
2070        }
2071        children();
2072    }
2073}
2074
2075
2076
2077// Function&Module: pie_slice()
2078// Synopsis: Creates a pie slice shape.
2079// SynTags: Geom, VNF
2080// Topics: Shapes (3D), Attachable, VNF Generators
2081// See Also: wedge()
2082// Description:
2083//   Creates a pie slice shape.
2084//
2085// Usage: As Module
2086//   pie_slice(l|h=|height=|length=, r, ang, [center]);
2087//   pie_slice(l|h=|height=|length=, d=, ang=, ...);
2088//   pie_slice(l|h=|height=|length=, r1=|d1=, r2=|d2=, ang=, ...);
2089// Usage: As Function
2090//   vnf = pie_slice(l|h=|height=|length=, r, ang, [center]);
2091//   vnf = pie_slice(l|h=|height=|length=, d=, ang=, ...);
2092//   vnf = pie_slice(l|h=|height=|length=, r1=|d1=, r2=|d2=, ang=, ...);
2093// Usage: Attaching Children
2094//   pie_slice(l|h, r, ang, ...) ATTACHMENTS;
2095//
2096// Arguments:
2097//   h / l / height / length = height of pie slice.
2098//   r = radius of pie slice.
2099//   ang = pie slice angle in degrees.
2100//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=UP`.
2101//   ---
2102//   r1 = bottom radius of pie slice.
2103//   r2 = top radius of pie slice.
2104//   d = diameter of pie slice.
2105//   d1 = bottom diameter of pie slice.
2106//   d2 = top diameter of pie slice.
2107//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
2108//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
2109//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
2110//
2111// Example: Cylindrical Pie Slice
2112//   pie_slice(ang=45, l=20, r=30);
2113// Example: Conical Pie Slice
2114//   pie_slice(ang=60, l=20, d1=50, d2=70);
2115// Example: Big Slice
2116//   pie_slice(ang=300, l=20, d1=50, d2=70);
2117// Example: Generating a VNF
2118//   vnf = pie_slice(ang=150, l=20, r1=30, r2=50);
2119//   vnf_polyhedron(vnf);
2120
2121module pie_slice(
2122    h, r, ang=30, center,
2123    r1, r2, d, d1, d2, l, length, height,
2124    anchor, spin=0, orient=UP
2125) {
2126    l = one_defined([l, h,height,length],"l,h,height,length",dflt=1);
2127    r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=10);
2128    r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=10);
2129    maxd = max(r1,r2)+0.1;
2130    anchor = get_anchor(anchor, center, BOT, BOT);
2131    attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) {
2132        difference() {
2133            cyl(r1=r1, r2=r2, h=l);
2134            if (ang<180) rotate(ang) back(maxd/2) cube([2*maxd, maxd, l+0.1], center=true);
2135            difference() {
2136                fwd(maxd/2) cube([2*maxd, maxd, l+0.2], center=true);
2137                if (ang>180) rotate(ang-180) back(maxd/2) cube([2*maxd, maxd, l+0.1], center=true);
2138            }
2139        }
2140        children();
2141    }
2142}
2143
2144function pie_slice(
2145    h, r, ang=30, center,
2146    r1, r2, d, d1, d2, l, length, height,
2147    anchor, spin=0, orient=UP
2148) = let(
2149        anchor = get_anchor(anchor, center, BOT, BOT),
2150        l = one_defined([l, h,height,length],"l,h,height,length",dflt=1),
2151        r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=10),
2152        r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=10),
2153        maxd = max(r1,r2)+0.1,
2154        sides = ceil(segs(max(r1,r2))*ang/360),
2155        step = ang/sides,
2156        vnf = vnf_vertex_array(
2157            points=[
2158                for (u = [0,1]) let(
2159                    h = lerp(-l/2,l/2,u),
2160                    r = lerp(r1,r2,u)
2161                ) [
2162                    for (theta = [0:step:ang+EPSILON])
2163                        cylindrical_to_xyz(r,theta,h),
2164                    [0,0,h]
2165                ]
2166            ],
2167            col_wrap=true, caps=true, reverse=true
2168        )
2169    ) reorient(anchor,spin,orient, r1=r1, r2=r2, l=l, p=vnf);
2170
2171
2172
2173// Section: Other Round Objects
2174
2175
2176// Function&Module: sphere()
2177// Synopsis: Creates an attachable spherical object.
2178// SynTags: Geom, VNF, Ext
2179// Topics: Shapes (3D), Attachable, VNF Generators
2180// See Also: spheroid()
2181// Usage: As Module (native OpenSCAD)
2182//   sphere(r|d=);
2183// Usage: Using BOSL2 attachments extensions
2184//   sphere(r|d=, [anchor=], [spin=], [orient=]) [ATTACHMENTS];
2185// Usage: As Function (BOSL2 extension)
2186//   vnf = sphere(r|d=, [anchor=], [spin=], [orient=]) [ATTACHMENTS];
2187// Description:
2188//   Creates a sphere object.
2189//   This module extends the built-in `sphere()` module by providing support for BOSL2 anchoring and attachments, and a function form. 
2190//   When called as a function, returns a [VNF](vnf.scad) for a sphere.
2191// Arguments:
2192//   r = Radius of the sphere.
2193//   ---
2194//   d = Diameter of the sphere.
2195//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
2196//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
2197//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
2198// Example: By Radius
2199//   sphere(r=50);
2200// Example: By Diameter
2201//   sphere(d=100);
2202// Example: Anchoring
2203//   sphere(d=100, anchor=FRONT);
2204// Example: Spin
2205//   sphere(d=100, anchor=FRONT, spin=45);
2206// Example: Orientation
2207//   sphere(d=100, anchor=FRONT, spin=45, orient=FWD);
2208// Example: Standard Connectors
2209//   sphere(d=50) show_anchors();
2210
2211module sphere(r, d, anchor=CENTER, spin=0, orient=UP) {
2212    r = get_radius(r=r, d=d, dflt=1);
2213    attachable(anchor,spin,orient, r=r) {
2214            _sphere(r=r);
2215            children();
2216    }
2217}
2218
2219function sphere(r, d, anchor=CENTER, spin=0, orient=UP) =
2220    spheroid(r=r, d=d, style="orig", anchor=anchor, spin=spin, orient=orient);
2221
2222
2223// Function&Module: spheroid()
2224// Synopsis: Creates an attachable spherical object with controllable triangulation.
2225// SynTags: Geom, VNF
2226// Topics: Shapes (3D), Attachable, VNF Generators
2227// See Also: sphere()
2228// Usage: Typical
2229//   spheroid(r|d, [circum], [style]) [ATTACHMENTS];
2230// Usage: As Function
2231//   vnf = spheroid(r|d, [circum], [style]);
2232// Description:
2233//   Creates a spheroid object, with support for anchoring and attachments.
2234//   This is a drop-in replacement for the built-in `sphere()` module.
2235//   When called as a function, returns a [VNF](vnf.scad) for a spheroid.
2236//   The exact triangulation of this spheroid can be controlled via the `style=`
2237//   argument, where the value can be one of `"orig"`, `"aligned"`, `"stagger"`,
2238//   `"octa"`, or `"icosa"`.
2239//   - `style="orig"` constructs a sphere the same way that the OpenSCAD `sphere()` built-in does.
2240//   - `style="aligned"` constructs a sphere where, if `$fn` is a multiple of 4, it has vertices at all axis maxima and minima.  ie: its bounding box is exactly the sphere diameter in length on all three axes.  This is the default.
2241//   - `style="stagger"` forms a sphere where all faces are triangular, but the top and bottom poles have thinner triangles.
2242//   - `style="octa"` forms a sphere by subdividing an octahedron.  This makes more uniform faces over the entirety of the sphere, and guarantees the bounding box is the sphere diameter in size on all axes.  The effective `$fn` value is quantized to a multiple of 4.  This is used in constructing rounded corners for various other shapes.
2243//   - `style="icosa"` forms a sphere by subdividing an icosahedron.  This makes even more uniform faces over the whole sphere.  The effective `$fn` value is quantized to a multiple of 5.  This sphere has a guaranteed bounding box when `$fn` is a multiple of 10.
2244//   .
2245//   By default the object spheroid() produces is a polyhedron whose vertices all lie on the requested sphere.  This means
2246//   the approximating polyhedron is inscribed in the sphere.
2247//   The `circum` argument requests a circumscribing sphere, where the true sphere is
2248//   inside and tangent to all the faces of the approximating polyhedron.  To produce
2249//   a circumscribing polyhedron, we use the dual polyhedron of the basic form.  The dual of a polyhedron is
2250//   a new polyhedron whose vertices are obtained from the faces of the parent polyhedron.
2251//   The "orig" and "align" forms are duals of each other.  If you request a circumscribing polyhedron in
2252//   these styles then the polyhedron will look the same as the default inscribing form.  But for the other
2253//   styles, the duals are completely different from their parents, and from each other.  Generation of the circumscribed versions (duals)
2254//   for "octa" and "icosa" is fast if you use the module form but can be very slow (several minutes) if you use the functional
2255//   form and choose a large $fn value.
2256//   .
2257//   With style="align", the circumscribed sphere has its maximum radius on the X and Y axes
2258//   but is undersized on the Z axis.  With style="octa" the circumscribed sphere has faces at each axis, so
2259//   the radius on the axes is equal to the specified radius, which is the *minimum* radius of the circumscribed sphere.
2260//   The same thing is true for style="icosa" when $fn is a multiple of 10.  This would enable you to create spherical
2261//   holes with guaranteed on-axis dimensions.
2262// Arguments:
2263//   r = Radius of the spheroid.
2264//   style = The style of the spheroid's construction. One of "orig", "aligned", "stagger", "octa", or "icosa".  Default: "aligned"
2265//   ---
2266//   d = Diameter of the spheroid.
2267//   circum = If true, the approximate sphere circumscribes the true sphere of the requested size.  Otherwise inscribes.  Note that for some styles, the circumscribed sphere looks different than the inscribed sphere.  Default: false (inscribes)
2268//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
2269//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
2270//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
2271// Example: By Radius
2272//   spheroid(r=50);
2273// Example: By Diameter
2274//   spheroid(d=100);
2275// Example: style="orig"
2276//   spheroid(d=100, style="orig", $fn=10);
2277// Example: style="aligned"
2278//   spheroid(d=100, style="aligned", $fn=10);
2279// Example: style="stagger"
2280//   spheroid(d=100, style="stagger", $fn=10);
2281// Example: style="stagger" with circum=true
2282//   spheroid(d=100, style="stagger", circum=true, $fn=10);
2283// Example: style="octa", octahedral based tesselation.  In this style, $fn is quantized to a multiple of 4.
2284//   spheroid(d=100, style="octa", $fn=10);
2285// Example: style="octa", with circum=true, produces mostly very irregular hexagonal faces
2286//   spheroid(d=100, style="octa", circum=true, $fn=16);
2287// Example: style="icosa", icosahedral based tesselation.  In this style, $fn is quantized to a multiple of 5.
2288//   spheroid(d=100, style="icosa", $fn=10);
2289// Example: style="icosa", circum=true.  This style has hexagons and 12 pentagons, similar to (but not the same as) a soccer ball.
2290//   spheroid(d=100, style="icosa", circum=true, $fn=10);
2291// Example: Anchoring
2292//   spheroid(d=100, anchor=FRONT);
2293// Example: Spin
2294//   spheroid(d=100, anchor=FRONT, spin=45);
2295// Example: Orientation
2296//   spheroid(d=100, anchor=FRONT, spin=45, orient=FWD);
2297// Example: Standard Connectors
2298//   spheroid(d=50) show_anchors();
2299// Example: Called as Function
2300//   vnf = spheroid(d=100, style="icosa");
2301//   vnf_polyhedron(vnf);
2302// Example: With "orig" the circumscribing sphere has the same form.  The green sphere is a tiny bit oversized so it pokes through the low points in the circumscribed sphere with low $fn.  This demonstrates that these spheres are in fact circumscribing.
2303//   color("green")spheroid(r=10.01, $fn=256);
2304//   spheroid(r=10, style="orig", circum=true, $fn=16);
2305// Example: With "aligned" the same is true: the circumscribing sphere is also aligned, if $fn is divisible by 4.
2306//   color("green")spheroid(r=10.01, $fn=256);
2307//   spheroid(r=10, style="aligned", circum=true, $fn=16);
2308// Example: For the other styles, the circumscribing sphere is different, as shown here with "stagger"
2309//   color("green")spheroid(r=10.01, $fn=256);
2310//   spheroid(r=10, style="stagger", circum=true, $fn=16);
2311// Example: The dual of "octa" that provides the circumscribing sphere has weird asymmetric hexagonal faces:
2312//   color("green")spheroid(r=10.01, $fn=256);
2313//   spheroid(r=10, style="octa", circum=true, $fn=16);
2314// Example: The dual of "icosa" features hexagons and always 12 pentagons:
2315//   color("green")spheroid(r=10.01, $fn=256);
2316//   spheroid(r=10, style="icosa", circum=true, $fn=16);
2317
2318module spheroid(r, style="aligned", d, circum=false, dual=false, anchor=CENTER, spin=0, orient=UP)
2319{
2320    r = get_radius(r=r, d=d, dflt=1);
2321    sides = segs(r);
2322    vsides = ceil(sides/2);
2323    attachable(anchor,spin,orient, r=r) {
2324        if (style=="orig" && !circum) {
2325            merids = [ for (i=[0:1:vsides-1]) 90-(i+0.5)*180/vsides ];
2326            path = [
2327                let(a = merids[0]) [0, sin(a)],
2328                for (a=merids) [cos(a), sin(a)],
2329                let(a = last(merids)) [0, sin(a)]
2330            ];
2331            scale(r) rotate(180) rotate_extrude(convexity=2,$fn=sides) polygon(path);
2332        }
2333        // Don't now how to construct faces for these efficiently, so use hull_points, which
2334        // is very much faster than using hull() as happens in the spheroid() function
2335        else if (circum && (style=="octa" || style=="icosa")) {
2336            orig_sphere = spheroid(r,style,circum=false);
2337            dualvert = _dual_vertices(orig_sphere);
2338            hull_points(dualvert,fast=true);
2339        } else {
2340            vnf = spheroid(r=r, circum=circum, style=style);
2341            vnf_polyhedron(vnf, convexity=2);
2342        }
2343        children();
2344    }
2345}
2346
2347
2348// p is a list of 3 points defining a triangle in any dimension.  N is the number of extra points
2349// to add, so output triangle has N+2 points on each side.
2350function _subsample_triangle(p,N) =
2351    [for(i=[0:N+1]) [for (j=[0:N+1-i]) unit(lerp(p[0],p[1],i/(N+1)) + (p[2]-p[0])*j/(N+1))]];
2352
2353
2354// Input should have only triangular faces
2355function _dual_vertices(vnf) =
2356  let(vert=vnf[0])
2357  [for(face=vnf[1])
2358      let(planes = select(vert,face))
2359      //linear_solve3(planes, [for(p=planes) p*p])
2360      linear_solve3(select(planes,0,2), [for(i=[0:2]) planes[i]*planes[i]]) // Handle larger faces, maybe?
2361  ];
2362
2363
2364function spheroid(r, style="aligned", d, circum=false, anchor=CENTER, spin=0, orient=UP) =
2365    let(
2366        r = get_radius(r=r, d=d, dflt=1),
2367        hsides = segs(r),
2368        vsides = max(2,ceil(hsides/2)),
2369        octa_steps = round(max(4,hsides)/4),
2370        icosa_steps = round(max(5,hsides)/5),
2371        stagger = style=="stagger"
2372     )
2373     circum && style=="orig" ?
2374         let(
2375              orig_sphere = spheroid(r,"aligned",circum=false),
2376              dualvert = zrot(360/hsides/2,_dual_vertices(orig_sphere)),
2377              culledvert = [
2378                              [for(i=[0:2:2*hsides-1]) dualvert[i]],
2379                              for(j=[1:vsides-2])
2380                                 [for(i=[0:2:2*hsides-1]) dualvert[j*2*hsides+i]],
2381                              [for(i=[1:2:2*hsides-1]) dualvert[i]]
2382                           ],
2383              vnf = vnf_vertex_array(culledvert,col_wrap=true,caps=true)
2384          )
2385          [reorient(anchor,spin,orient, r=r, p=vnf[0]), vnf[1]]
2386     :
2387     circum && (style=="octa" || style=="icosa") ?
2388         let(
2389              orig_sphere = spheroid(r,style,circum=false),
2390              dualvert = _dual_vertices(orig_sphere),
2391              faces = hull(dualvert)
2392         )
2393         [reorient(anchor,spin,orient, r=r, p=dualvert), faces]
2394     :
2395     style=="icosa" ?    // subdivide faces of an icosahedron and project them onto a sphere
2396         let(
2397             N = icosa_steps-1,
2398             // construct an icosahedron
2399             icovert=[ for(i=[-1,1], j=[-1,1]) each [[0,i,j*PHI], [i,j*PHI,0], [j*PHI,0,i]]],
2400             icoface = hull(icovert),
2401             // Subsample face 0 of the icosahedron
2402             face0 = select(icovert,icoface[0]),
2403             sampled = r * _subsample_triangle(face0,N),
2404             dir0 = mean(face0),
2405             point0 = face0[0]-dir0,
2406             // Make a rotated copy of the subsampled triangle on each icosahedral face
2407             tri_list = [sampled,
2408                         for(i=[1:1:len(icoface)-1])
2409                 let(face = select(icovert,icoface[i]))
2410                 apply(frame_map(z=mean(face),x=face[0]-mean(face))
2411                        *frame_map(z=dir0,x=point0,reverse=true),
2412                       sampled)],
2413             // faces for the first triangle group
2414             faces = vnf_tri_array(tri_list[0],reverse=true)[1],
2415             size = repeat((N+2)*(N+3)/2,3),
2416             // Expand to full face list
2417             fullfaces = [for(i=idx(tri_list)) each [for(f=faces) f+i*size]],
2418             fullvert = flatten(flatten(tri_list))    // eliminate triangle structure
2419         )
2420         [reorient(anchor,spin,orient, r=r, p=fullvert), fullfaces]
2421     :
2422     let(
2423        verts = circum && style=="stagger" ? _dual_vertices(spheroid(r,style,circum=false))
2424              : circum && style=="aligned" ?
2425                     let(
2426                         orig_sphere = spheroid(r,"orig",circum=false),
2427                         dualvert = _dual_vertices(orig_sphere),
2428                         culledvert = zrot(360/hsides/2,
2429                                           [dualvert[0],
2430                                            for(i=[2:2:len(dualvert)-1]) dualvert[i],
2431                                            dualvert[1]])
2432                      )
2433                      culledvert
2434              : style=="orig"? [
2435                                 for (i=[0:1:vsides-1])
2436                                     let(phi = (i+0.5)*180/(vsides))
2437                                     for (j=[0:1:hsides-1])
2438                                         let(theta = j*360/hsides)
2439                                         spherical_to_xyz(r, theta, phi),
2440                               ]
2441              : style=="aligned" || style=="stagger"?
2442                         [ spherical_to_xyz(r, 0, 0),
2443                           for (i=[1:1:vsides-1])
2444                               let(phi = i*180/vsides)
2445                               for (j=[0:1:hsides-1])
2446                                   let(theta = (j+((stagger && i%2!=0)?0.5:0))*360/hsides)
2447                                   spherical_to_xyz(r, theta, phi),
2448                           spherical_to_xyz(r, 0, 180)
2449                         ]
2450              : style=="octa"?
2451                      let(
2452                           meridians = [
2453                                        1,
2454                                        for (i = [1:1:octa_steps]) i*4,
2455                                        for (i = [octa_steps-1:-1:1]) i*4,
2456                                        1,
2457                                       ]
2458                      )
2459                      [
2460                       for (i=idx(meridians), j=[0:1:meridians[i]-1])
2461                           spherical_to_xyz(r, j*360/meridians[i], i*180/(len(meridians)-1))
2462                      ]
2463              : assert(in_list(style,["orig","aligned","stagger","octa","icosa"])),
2464        lv = len(verts),
2465        faces = circum && style=="stagger" ?
2466                     let(ptcount=2*hsides)
2467                     [
2468                       [for(i=[ptcount-2:-2:0]) i],
2469                       for(j=[0:hsides-1])
2470                           [j*2, (j*2+2)%ptcount,ptcount+(j*2+2)%ptcount,ptcount+(j*2+3)%ptcount,ptcount+j*2],
2471                       for(i=[1:vsides-3])
2472                           let(base=ptcount*i)
2473                           for(j=[0:hsides-1])
2474                               i%2==0 ? [base+2*j, base+(2*j+1)%ptcount, base+(2*j+2)%ptcount,
2475                                        base+ptcount+(2*j)%ptcount, base+ptcount+(2*j+1)%ptcount, base+ptcount+(2*j-2+ptcount)%ptcount]
2476                                      : [base+(1+2*j)%ptcount, base+(2*j)%ptcount, base+(2*j+3)%ptcount,
2477                                         base+ptcount+(3+2*j)%ptcount, base+ptcount+(2*j+2)%ptcount,base+ptcount+(2*j+1)%ptcount],
2478                       for(j=[0:hsides-1])
2479                          vsides%2==0
2480                            ? [(j*2+3)%ptcount, j*2+1, lv-ptcount+(2+j*2)%ptcount, lv-ptcount+(3+j*2)%ptcount, lv-ptcount+(4+j*2)%ptcount]
2481                            : [(j*2+3)%ptcount, j*2+1, lv-ptcount+(1+j*2)%ptcount, lv-ptcount+(j*2)%ptcount, lv-ptcount+(3+j*2)%ptcount],
2482                       [for(i=[1:2:ptcount-1]) i],
2483                     ]
2484              : style=="aligned" || style=="stagger" ?  // includes case of aligned with circum == true
2485                     [
2486                       for (i=[0:1:hsides-1])
2487                           let(b2 = lv-2-hsides)
2488                           each [
2489                                 [i+1, 0, ((i+1)%hsides)+1],
2490                                 [lv-1, b2+i+1, b2+((i+1)%hsides)+1],
2491                                ],
2492                       for (i=[0:1:vsides-3], j=[0:1:hsides-1])
2493                           let(base = 1 + hsides*i)
2494                           each (
2495                                 (stagger && i%2!=0)? [
2496                                     [base+j, base+hsides+j%hsides, base+hsides+(j+hsides-1)%hsides],
2497                                     [base+j, base+(j+1)%hsides, base+hsides+j],
2498                                 ] : [
2499                                     [base+j, base+(j+1)%hsides, base+hsides+(j+1)%hsides],
2500                                     [base+j, base+hsides+(j+1)%hsides, base+hsides+j],
2501                                 ]
2502                           )
2503                     ]
2504              : style=="orig"? [
2505                                [for (i=[0:1:hsides-1]) hsides-i-1],
2506                                [for (i=[0:1:hsides-1]) lv-hsides+i],
2507                                for (i=[0:1:vsides-2], j=[0:1:hsides-1])
2508                                    each [
2509                                          [(i+1)*hsides+j, i*hsides+j, i*hsides+(j+1)%hsides],
2510                                          [(i+1)*hsides+j, i*hsides+(j+1)%hsides, (i+1)*hsides+(j+1)%hsides],
2511                                    ]
2512                               ]
2513              : /*style=="octa"?*/
2514                     let(
2515                         meridians = [
2516                                      0, 1,
2517                                      for (i = [1:1:octa_steps]) i*4,
2518                                      for (i = [octa_steps-1:-1:1]) i*4,
2519                                      1,
2520                                     ],
2521                         offs = cumsum(meridians),
2522                         pc = last(offs)-1,
2523                         os = octa_steps * 2
2524                     )
2525                     [
2526                      for (i=[0:1:3]) [0, 1+(i+1)%4, 1+i],
2527                      for (i=[0:1:3]) [pc-0, pc-(1+(i+1)%4), pc-(1+i)],
2528                      for (i=[1:1:octa_steps-1])
2529                          let(m = meridians[i+2]/4)
2530                          for (j=[0:1:3], k=[0:1:m-1])
2531                              let(
2532                                  m1 = meridians[i+1],
2533                                  m2 = meridians[i+2],
2534                                  p1 = offs[i+0] + (j*m1/4 + k+0) % m1,
2535                                  p2 = offs[i+0] + (j*m1/4 + k+1) % m1,
2536                                  p3 = offs[i+1] + (j*m2/4 + k+0) % m2,
2537                                  p4 = offs[i+1] + (j*m2/4 + k+1) % m2,
2538                                  p5 = offs[os-i+0] + (j*m1/4 + k+0) % m1,
2539                                  p6 = offs[os-i+0] + (j*m1/4 + k+1) % m1,
2540                                  p7 = offs[os-i-1] + (j*m2/4 + k+0) % m2,
2541                                  p8 = offs[os-i-1] + (j*m2/4 + k+1) % m2
2542                              )
2543                              each [
2544                                    [p1, p4, p3],
2545                                    if (k<m-1) [p1, p2, p4],
2546                                    [p5, p7, p8],
2547                                    if (k<m-1) [p5, p8, p6],
2548                                   ],
2549                     ]
2550    ) [reorient(anchor,spin,orient, r=r, p=verts), faces];
2551
2552
2553
2554// Function&Module: torus()
2555// Synopsis: Creates an attachable torus.
2556// SynTags: Geom, VNF
2557// Topics: Shapes (3D), Attachable, VNF Generators
2558// See Also: spheroid(), cyl()
2559//
2560// Usage: As Module
2561//   torus(r_maj|d_maj, r_min|d_min, [center], ...) [ATTACHMENTS];
2562//   torus(or|od, ir|id, ...) [ATTACHMENTS];
2563//   torus(r_maj|d_maj, or|od, ...) [ATTACHMENTS];
2564//   torus(r_maj|d_maj, ir|id, ...) [ATTACHMENTS];
2565//   torus(r_min|d_min, or|od, ...) [ATTACHMENTS];
2566//   torus(r_min|d_min, ir|id, ...) [ATTACHMENTS];
2567// Usage: As Function
2568//   vnf = torus(r_maj|d_maj, r_min|d_min, [center], ...);
2569//   vnf = torus(or|od, ir|id, ...);
2570//   vnf = torus(r_maj|d_maj, or|od, ...);
2571//   vnf = torus(r_maj|d_maj, ir|id, ...);
2572//   vnf = torus(r_min|d_min, or|od, ...);
2573//   vnf = torus(r_min|d_min, ir|id, ...);
2574//
2575// Description:
2576//   Creates an attachable toroidal shape.
2577//
2578// Figure(2D,Med):
2579//   module dashcirc(r,start=0,angle=359.9,dashlen=5) let(step=360*dashlen/(2*r*PI)) for(a=[start:step:start+angle]) stroke(arc(r=r,start=a,angle=step/2));
2580//   r = 75; r2 = 30;
2581//   down(r2+0.1) #torus(r_maj=r, r_min=r2, $fn=72);
2582//   color("blue") linear_extrude(height=0.01) {
2583//       dashcirc(r=r,start=15,angle=45);
2584//       dashcirc(r=r-r2, start=90+15, angle=60);
2585//       dashcirc(r=r+r2, start=180+45, angle=30);
2586//       dashcirc(r=r+r2, start=15, angle=30);
2587//   }
2588//   rot(240) color("blue") linear_extrude(height=0.01) {
2589//       stroke([[0,0],[r+r2,0]], endcaps="arrow2",width=2);
2590//       right(r) fwd(9) rot(-240) text("or",size=10,anchor=CENTER);
2591//   }
2592//   rot(135) color("blue") linear_extrude(height=0.01) {
2593//       stroke([[0,0],[r-r2,0]], endcaps="arrow2",width=2);
2594//       right((r-r2)/2) back(8) rot(-135) text("ir",size=10,anchor=CENTER);
2595//   }
2596//   rot(45) color("blue") linear_extrude(height=0.01) {
2597//       stroke([[0,0],[r,0]], endcaps="arrow2",width=2);
2598//       right(r/2) back(8) text("r_maj",size=9,anchor=CENTER);
2599//   }
2600//   rot(30) color("blue") linear_extrude(height=0.01) {
2601//       stroke([[r,0],[r+r2,0]], endcaps="arrow2",width=2);
2602//       right(r+r2/2) fwd(8) text("r_min",size=7,anchor=CENTER);
2603//   }
2604//
2605// Arguments:
2606//   r_maj = major radius of torus ring. (use with 'r_min', or 'd_min')
2607//   r_min = minor radius of torus ring. (use with 'r_maj', or 'd_maj')
2608//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=DOWN`.
2609//   ---
2610//   d_maj  = major diameter of torus ring. (use with 'r_min', or 'd_min')
2611//   d_min = minor diameter of torus ring. (use with 'r_maj', or 'd_maj')
2612//   or = outer radius of the torus. (use with 'ir', or 'id')
2613//   ir = inside radius of the torus. (use with 'or', or 'od')
2614//   od = outer diameter of the torus. (use with 'ir' or 'id')
2615//   id = inside diameter of the torus. (use with 'or' or 'od')
2616//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
2617//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
2618//
2619// Example:
2620//   // These all produce the same torus.
2621//   torus(r_maj=22.5, r_min=7.5);
2622//   torus(d_maj=45, d_min=15);
2623//   torus(or=30, ir=15);
2624//   torus(od=60, id=30);
2625//   torus(d_maj=45, id=30);
2626//   torus(d_maj=45, od=60);
2627//   torus(d_min=15, id=30);
2628//   torus(d_min=15, od=60);
2629//   vnf_polyhedron(torus(d_min=15, od=60), convexity=4);
2630// Example: Standard Connectors
2631//   torus(od=60, id=30) show_anchors();
2632
2633module torus(
2634    r_maj, r_min, center,
2635    d_maj, d_min,
2636    or, od, ir, id,
2637    anchor, spin=0, orient=UP
2638) {
2639    _or = get_radius(r=or, d=od, dflt=undef);
2640    _ir = get_radius(r=ir, d=id, dflt=undef);
2641    _r_maj = get_radius(r=r_maj, d=d_maj, dflt=undef);
2642    _r_min = get_radius(r=r_min, d=d_min, dflt=undef);
2643    maj_rad = is_finite(_r_maj)? _r_maj :
2644        is_finite(_ir) && is_finite(_or)? (_or + _ir)/2 :
2645        is_finite(_ir) && is_finite(_r_min)? (_ir + _r_min) :
2646        is_finite(_or) && is_finite(_r_min)? (_or - _r_min) :
2647        assert(false, "Bad Parameters");
2648    min_rad = is_finite(_r_min)? _r_min :
2649        is_finite(_ir)? (maj_rad - _ir) :
2650        is_finite(_or)? (_or - maj_rad) :
2651        assert(false, "Bad Parameters");
2652    anchor = get_anchor(anchor, center, BOT, CENTER);
2653    attachable(anchor,spin,orient, r=(maj_rad+min_rad), l=min_rad*2) {
2654        rotate_extrude(convexity=4) {
2655            right_half(s=min_rad*2, planar=true)
2656                right(maj_rad)
2657                    circle(r=min_rad);
2658        }
2659        children();
2660    }
2661}
2662
2663
2664function torus(
2665    r_maj, r_min, center,
2666    d_maj, d_min,
2667    or, od, ir, id,
2668    anchor, spin=0, orient=UP
2669) = let(
2670    _or = get_radius(r=or, d=od, dflt=undef),
2671    _ir = get_radius(r=ir, d=id, dflt=undef),
2672    _r_maj = get_radius(r=r_maj, d=d_maj, dflt=undef),
2673    _r_min = get_radius(r=r_min, d=d_min, dflt=undef),
2674    maj_rad = is_finite(_r_maj)? _r_maj :
2675        is_finite(_ir) && is_finite(_or)? (_or + _ir)/2 :
2676        is_finite(_ir) && is_finite(_r_min)? (_ir + _r_min) :
2677        is_finite(_or) && is_finite(_r_min)? (_or - _r_min) :
2678        assert(false, "Bad Parameters"),
2679    min_rad = is_finite(_r_min)? _r_min :
2680        is_finite(_ir)? (maj_rad - _ir) :
2681        is_finite(_or)? (_or - maj_rad) :
2682        assert(false, "Bad Parameters"),
2683    anchor = get_anchor(anchor, center, BOT, CENTER),
2684    maj_sides = segs(maj_rad+min_rad),
2685    maj_step = 360 / maj_sides,
2686    min_sides = segs(min_rad),
2687    min_step = 360 / min_sides,
2688    xyprofile = min_rad <= maj_rad? right(maj_rad, p=circle(r=min_rad)) :
2689        right_half(p=right(maj_rad, p=circle(r=min_rad)))[0],
2690    profile = xrot(90, p=path3d(xyprofile)),
2691    vnf = vnf_vertex_array(
2692        points=[for (a=[0:maj_step:360-EPSILON]) zrot(a, p=profile)],
2693        caps=false, col_wrap=true, row_wrap=true, reverse=true
2694    )
2695) reorient(anchor,spin,orient, r=(maj_rad+min_rad), l=min_rad*2, p=vnf);
2696
2697
2698// Function&Module: teardrop()
2699// Synopsis: Creates a teardrop shape.
2700// SynTags: Geom, VNF
2701// Topics: Shapes (3D), Attachable, VNF Generators, FDM Optimized
2702// See Also: onion(), teardrop2d()
2703// Description:
2704//   Makes a teardrop shape in the XZ plane. Useful for 3D printable holes.
2705//   Optional chamfers can be added with positive or negative distances.  A positive distance
2706//   specifies the amount to inset the chamfer along the front/back faces of the shape.
2707//   The chamfer will extend the same y distance into the shape.  If the radii are the same
2708//   then the chamfer will be a 45 degree chamfer, but in other cases it will not.
2709//   Note that with caps, the chamfer must not be so big that it makes the cap height illegal.  
2710//
2711// Usage: Typical
2712//   teardrop(h|l=|length=|height=, r, [ang], [cap_h], [chamfer=], ...) [ATTACHMENTS];
2713//   teardrop(h|l=|length=|height=, d=, [ang=], [cap_h=], [chamfer=], ...) [ATTACHMENTS];
2714// Usage: Psuedo-Conical
2715//   teardrop(h|l=|height=|length=, r1=, r2=, [ang=], [cap_h1=], [cap_h2=], ...)  [ATTACHMENTS];
2716//   teardrop(h|l=|height=|length=, d1=, d2=, [ang=], [cap_h1=], [cap_h2=], ...)  [ATTACHMENTS];
2717// Usage: As Function
2718//   vnf = teardrop(h|l=|height=|length=, r|d=, [ang=], [cap_h=], ...);
2719//   vnf = teardrop(h|l=|height=|length=, r1=|d1=, r2=|d2=, [ang=], [cap_h=], ...);
2720//   vnf = teardrop(h|l=|height=|length=, r1=|d1=, r2=|d2=, [ang=], [cap_h1=], [cap_h2=], ...);
2721//
2722// Arguments:
2723//   h / l / height / length = Thickness of teardrop. Default: 1
2724//   r = Radius of circular part of teardrop.  Default: 1
2725//   ang = Angle of hat walls from the Z axis.  Default: 45 degrees
2726//   cap_h = If given, height above center where the shape will be truncated. Default: `undef` (no truncation)
2727//   ---
2728//   circum = produce a circumscribing teardrop shape.  Default: false
2729//   r1 = Radius of circular portion of the front end of the teardrop shape.
2730//   r2 = Radius of circular portion of the back end of the teardrop shape.
2731//   d = Diameter of circular portion of the teardrop shape.
2732//   d1 = Diameter of circular portion of the front end of the teardrop shape.
2733//   d2 = Diameter of circular portion of the back end of the teardrop shape.
2734//   cap_h1 = If given, height above center where the shape will be truncated, on the front side. Default: `undef` (no truncation)
2735//   cap_h2 = If given, height above center where the shape will be truncated, on the back side. Default: `undef` (no truncation)
2736//   chamfer = Specifies size of chamfer as distance along the bottom and top faces.  Default: 0
2737//   chamfer1 = Specifies size of chamfer on bottom as distance along bottom face.  Default: 0
2738//   chamfer2 = Specifies size of chamfer on top as distance along top face.  Default: 0
2739//   realign = Passes realign option to teardrop2d, which shifts face alignment.  Default: false
2740//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
2741//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
2742//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
2743//
2744// Named Anchors:
2745//   "cap" = The center of the top of the cap, oriented with the cap face normal.
2746//   "cap_fwd" = The front edge of the cap.
2747//   "cap_back" = The back edge of the cap.
2748//
2749// Example: Typical Shape
2750//   teardrop(r=30, h=10, ang=30);
2751// Example: Crop Cap
2752//   teardrop(r=30, h=10, ang=30, cap_h=40);
2753// Example: Close Crop
2754//   teardrop(r=30, h=10, ang=30, cap_h=20);
2755// Example: Psuedo-Conical
2756//   teardrop(r1=20, r2=30, h=40, cap_h1=25, cap_h2=35);
2757// Example: Adding chamfers can be useful for a teardrop hole mask
2758//   teardrop(r=10, l=50, chamfer1=2, chamfer2=-1.5);
2759// Example: Getting a VNF
2760//   vnf = teardrop(r1=25, r2=30, l=20, cap_h1=25, cap_h2=35);
2761//   vnf_polyhedron(vnf);
2762// Example: Standard Conical Connectors
2763//   teardrop(d1=20, d2=30, h=20, cap_h1=11, cap_h2=16)
2764//       show_anchors(custom=false);
2765// Example(Spin,VPD=150,Med): Named Conical Connectors
2766//   teardrop(d1=20, d2=30, h=20, cap_h1=11, cap_h2=16)
2767//       show_anchors(std=false);
2768
2769module teardrop(h, r, ang=45, cap_h, r1, r2, d, d1, d2, cap_h1, cap_h2, l, length, height, circum=false, realign=false,
2770                chamfer, chamfer1, chamfer2,anchor=CENTER, spin=0, orient=UP)
2771{
2772    length = one_defined([l, h, length, height],"l,h,length,height");
2773    dummy=assert(is_finite(length) && length>0, "length must be positive");
2774    r1 = get_radius(r=r, r1=r1, d=d, d1=d1);
2775    r2 = get_radius(r=r, r1=r2, d=d, d1=d2);
2776    tip_y1 = r1/cos(90-ang);
2777    tip_y2 = r2/cos(90-ang);
2778    _cap_h1 = min(default(cap_h1, tip_y1), tip_y1);
2779    _cap_h2 = min(default(cap_h2, tip_y2), tip_y2);
2780    capvec = unit([0, _cap_h1-_cap_h2, length]);
2781    anchors = [
2782        named_anchor("cap",      [0,0,(_cap_h1+_cap_h2)/2], capvec),
2783        named_anchor("cap_fwd",  [0,-length/2,_cap_h1],         unit((capvec+FWD)/2)),
2784        named_anchor("cap_back", [0,+length/2,_cap_h2],         unit((capvec+BACK)/2), 180),
2785    ];
2786    attachable(anchor,spin,orient, r1=r1, r2=r2, l=length, axis=BACK, anchors=anchors)
2787    {
2788        vnf_polyhedron(teardrop(ang=ang,cap_h=cap_h,r1=r1,r2=r2,cap_h1=cap_h1,cap_h2=cap_h2,circum=circum,realign=realign,
2789                                length=length, chamfer1=chamfer1,chamfer2=chamfer2,chamfer=chamfer));
2790        children();
2791    }
2792}
2793
2794
2795function teardrop(h, r, ang=45, cap_h, r1, r2, d, d1, d2, cap_h1, cap_h2,  chamfer, chamfer1, chamfer2, circum=false, realign=false,
2796                  l, length, height, anchor=CENTER, spin=0, orient=UP) =
2797    let(
2798        r1 = get_radius(r=r, r1=r1, d=d, d1=d1, dflt=1),
2799        r2 = get_radius(r=r, r1=r2, d=d, d1=d2, dflt=1),
2800        length = one_defined([l, h, length, height],"l,h,length,height"),
2801        dummy0=assert(is_finite(length) && length>0, "length must be positive"),
2802        cap_h1 = first_defined([cap_h1, cap_h]),
2803        cap_h2 = first_defined([cap_h2, cap_h]),
2804        chamfer1 = first_defined([chamfer1,chamfer,0]),
2805        chamfer2 = first_defined([chamfer2,chamfer,0]),    
2806        sides = segs(max(r1,r2)),
2807        profile1 = teardrop2d(r=r1, ang=ang, cap_h=cap_h1, $fn=sides, circum=circum, realign=realign,_extrapt=true),
2808        profile2 = teardrop2d(r=r2, ang=ang, cap_h=cap_h2, $fn=sides, circum=circum, realign=realign,_extrapt=true),
2809        tip_y1 = r1/cos(90-ang),
2810        tip_y2 = r2/cos(90-ang),
2811        _cap_h1 = min(default(cap_h1, tip_y1), tip_y1),
2812        _cap_h2 = min(default(cap_h2, tip_y2), tip_y2),
2813        capvec = unit([0, _cap_h1-_cap_h2, length]),
2814        dummy=
2815          assert(abs(chamfer1)+abs(chamfer2) <= length,"chamfers are too big to fit in the length")
2816          assert(chamfer1<=r1 && chamfer2<=r2, "Chamfers cannot be larger than raduis")
2817          assert(is_undef(cap_h1) || cap_h1-chamfer1 > r1*sin(ang), "chamfer1 is too big to work with the specified cap_h1")
2818          assert(is_undef(cap_h2) || cap_h2-chamfer2 > r2*sin(ang), "chamfer2 is too big to work with the specified cap_h2"),
2819        cprof1 = r1==chamfer1 ? repeat([0,0],len(profile1))
2820                              : teardrop2d(r=r1-chamfer1, ang=ang, cap_h=u_add(cap_h1,-chamfer1), $fn=sides, circum=circum, realign=realign,_extrapt=true),
2821        cprof2 = r2==chamfer2 ? repeat([0,0],len(profile2))
2822                              : teardrop2d(r=r2-chamfer2, ang=ang, cap_h=u_add(cap_h2,-chamfer2), $fn=sides, circum=circum, realign=realign,_extrapt=true),
2823        anchors = [
2824            named_anchor("cap",      [0,0,(_cap_h1+_cap_h2)/2], capvec),
2825            named_anchor("cap_fwd",  [0,-length/2,_cap_h1],         unit((capvec+FWD)/2)),
2826            named_anchor("cap_back", [0,+length/2,_cap_h2],         unit((capvec+BACK)/2), 180),
2827        ],
2828        vnf = vnf_vertex_array(
2829            points = [
2830                if (chamfer1!=0) fwd(length/2, xrot(90, path3d(cprof1))),
2831                fwd(length/2-abs(chamfer1), xrot(90, path3d(profile1))),
2832                back(length/2-abs(chamfer2), xrot(90, path3d(profile2))),
2833                if (chamfer2!=0) back(length/2, xrot(90, path3d(cprof2))),
2834            ],
2835            caps=true, col_wrap=true, reverse=true
2836        )
2837    ) reorient(anchor,spin,orient, r1=r1, r2=r2, l=l, axis=BACK, anchors=anchors, p=vnf);
2838
2839
2840// Function&Module: onion()
2841// Synopsis: Creates an attachable onion-like shape.
2842// SynTags: Geom, VNF
2843// Topics: Shapes (3D), Attachable, VNF Generators, FDM Optimized
2844// See Also: teardrop(), teardrop2d()
2845// Description:
2846//   Creates a sphere with a conical hat, to make a 3D teardrop.
2847//
2848// Usage: As Module
2849//   onion(r|d=, [ang=], [cap_h=], [circum=], [realign=], ...) [ATTACHMENTS];
2850// Usage: As Function
2851//   vnf = onion(r|d=, [ang=], [cap_h=], [circum=], [realign=], ...);
2852//
2853// Arguments:
2854//   r = radius of spherical portion of the bottom. Default: 1
2855//   ang = Angle of cone on top from vertical. Default: 45 degrees
2856//   cap_h = If given, height above sphere center to truncate teardrop shape.  Default: `undef` (no truncation)
2857//   ---
2858//   circum = set to true to circumscribe the specified radius/diameter.  Default: False
2859//   realign = adjust point alignment to determine if bottom is flat or pointy.  Default: False
2860//   d = diameter of spherical portion of bottom.
2861//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
2862//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
2863//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
2864//
2865// Named Anchors:
2866//   "cap" = The center of the top of the cap, oriented with the cap face normal.
2867//   "tip" = The position where an un-capped onion would come to a point, oriented in the direction the point is from the center.
2868//
2869// Example: Typical Shape
2870//   onion(r=30, ang=30);
2871// Example: Crop Cap
2872//   onion(r=30, ang=30, cap_h=40);
2873// Example: Close Crop
2874//   onion(r=30, ang=30, cap_h=20);
2875// Example: Onions are useful for making the tops of large cylindrical voids.
2876//   difference() {
2877//       cuboid([100,50,100], anchor=FWD+BOT);
2878//       down(0.1)
2879//           cylinder(h=50,d=50,anchor=BOT)
2880//               attach(TOP)
2881//                   onion(d=50, cap_h=30);
2882//   }
2883// Example: Standard Connectors
2884//   onion(d=30, ang=30, cap_h=20) show_anchors();
2885
2886module onion(r, ang=45, cap_h, d, circum=false, realign=false, anchor=CENTER, spin=0, orient=UP)
2887{
2888    r = get_radius(r=r, d=d, dflt=1);
2889    xyprofile = teardrop2d(r=r, ang=ang, cap_h=cap_h, circum=circum, realign=realign);
2890    tip_h = max(column(xyprofile,1));
2891    _cap_h = min(default(cap_h,tip_h), tip_h);
2892    anchors = [
2893        ["cap", [0,0,_cap_h], UP, 0],
2894        ["tip", [0,0,tip_h], UP, 0]
2895    ];
2896    attachable(anchor,spin,orient, r=r, anchors=anchors) {
2897        rotate_extrude(convexity=2) {
2898            difference() {
2899                polygon(xyprofile);
2900                square([2*r,2*max(_cap_h,r)+1], anchor=RIGHT);
2901            }
2902        }
2903        children();
2904    }
2905}
2906
2907
2908function onion(r, ang=45, cap_h, d, anchor=CENTER, spin=0, orient=UP) =
2909    let(
2910        r = get_radius(r=r, d=d, dflt=1),
2911        xyprofile = right_half(p=teardrop2d(r=r, ang=ang, cap_h=cap_h))[0],
2912        profile = xrot(90, p=path3d(xyprofile)),
2913        tip_h = max(column(xyprofile,1)),
2914        _cap_h = min(default(cap_h,tip_h), tip_h),
2915        anchors = [
2916            ["cap", [0,0,_cap_h], UP, 0],
2917            ["tip", [0,0,tip_h], UP, 0]
2918        ],
2919        sides = segs(r),
2920        step = 360 / sides,
2921        vnf = vnf_vertex_array(
2922            points=[for (a = [0:step:360-EPSILON]) zrot(a, p=profile)],
2923            caps=false, col_wrap=true, row_wrap=true, reverse=true
2924        )
2925    ) reorient(anchor,spin,orient, r=r, anchors=anchors, p=vnf);
2926
2927
2928// Section: Text
2929
2930// Module: text3d()
2931// Synopsis: Creates an attachable 3d text block.
2932// SynTags: Geom
2933// Topics: Attachments, Text
2934// See Also: path_text(), text() 
2935// Usage:
2936//   text3d(text, [h], [size], [font], [language=], [script=], [direction=], [atype=], [anchor=], [spin=], [orient=]);
2937// Description:
2938//   Creates a 3D text block that supports anchoring and single-parameter attachment to attachable objects.  You cannot attach children to text.
2939//   .
2940//   Historically fonts were specified by their "body size", the height of the metal body
2941//   on which the glyphs were cast.  This means the size was an upper bound on the size
2942//   of the font glyphs, not a direct measurement of their size.  In digital typesetting,
2943//   the metal body is replaced by an invisible box, the em square, whose side length is
2944//   defined to be the font's size.  The glyphs can be contained in that square, or they
2945//   can extend beyond it, depending on the choices made by the font designer.  As a
2946//   result, the meaning of font size varies between fonts: two fonts at the "same" size
2947//   can differ significantly in the actual size of their characters.  Typographers
2948//   customarily specify the size in the units of "points".  A point is 1/72 inch.  In
2949//   OpenSCAD, you specify the size in OpenSCAD units (often treated as millimeters for 3d
2950//   printing), so if you want points you will need to perform a suitable unit conversion.
2951//   In addition, the OpenSCAD font system has a bug: if you specify size=s you will
2952//   instead get a font whose size is s/0.72.  For many fonts this means the size of
2953//   capital letters will be approximately equal to s, because it is common for fonts to
2954//   use about 70% of their height for the ascenders in the font.  To get the customary
2955//   font size, you should multiply your desired size by 0.72.
2956//   .
2957//   To find the fonts that you have available in your OpenSCAD installation,
2958//   go to the Help menu and select "Font List".
2959// Arguments:
2960//   text = Text to create.
2961//   h / height / thickness = Extrusion height for the text.  Default: 1
2962//   size = The font will be created at this size divided by 0.72.   Default: 10
2963//   font = Font to use.  Default: "Liberation Sans" (standard OpenSCAD default)
2964//   ---
2965//   spacing = The relative spacing multiplier between characters.  Default: `1.0`
2966//   direction = The text direction.  `"ltr"` for left to right.  `"rtl"` for right to left. `"ttb"` for top to bottom. `"btt"` for bottom to top.  Default: `"ltr"`
2967//   language = The language the text is in.  Default: `"en"`
2968//   script = The script the text is in.  Default: `"latin"`
2969//   atype = Change vertical center between "baseline" and "ycenter".  Default: "baseline"
2970//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `"baseline"`
2971//   center = Center the text.  Equivalent to `atype="center", anchor=CENTER`.  Default: false
2972//   spin = Rotate this many degrees around the Z axis.  See [spin](attachments.scad#subsection-spin).  Default: `0`
2973//   orient = Vector to rotate top towards.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
2974// Anchor Types:
2975//   baseline = Anchor center is relative to text baseline
2976//   ycenter = Anchor center is relative to the actualy y direction center of the text
2977// Examples:
2978//   text3d("Fogmobar", h=3, size=10);
2979//   text3d("Fogmobar", h=2, size=12, font="Helvetica");
2980//   text3d("Fogmobar", h=2, anchor=CENTER);
2981//   text3d("Fogmobar", h=2, anchor=CENTER, atype="ycenter");
2982//   text3d("Fogmobar", h=2, anchor=RIGHT);
2983//   text3d("Fogmobar", h=2, anchor=RIGHT+BOT, atype="ycenter");
2984module text3d(text, h, size=10, font="Helvetica", spacing=1.0, direction="ltr", language="en", script="latin",
2985              height, thickness, atype, center=false,
2986              anchor, spin=0, orient=UP) {
2987    no_children($children);
2988    h = one_defined([h,height,thickness],"h,height,thickness",dflt=1);
2989    assert(is_undef(atype) || in_list(atype,["ycenter","baseline"]), "atype must be \"ycenter\" or \"baseline\"");
2990    assert(is_bool(center));
2991    assert(is_undef($attach_to),"text3d() does not support parent-child anchor attachment with two parameters");
2992    atype = default(atype, center?"ycenter":"baseline");
2993    anchor = default(anchor, center?CENTER:LEFT);
2994    geom = attach_geom(size=[size,size,h]);
2995    ha = anchor.x<0? "left" 
2996       : anchor.x>0? "right" 
2997       : "center";
2998    va = anchor.y<0? "bottom" 
2999       : anchor.y>0? "top" 
3000       : atype=="baseline"? "baseline"
3001       : "center";
3002    m = _attach_transform([0,0,anchor.z],spin,orient,geom);
3003    multmatrix(m) {
3004        $parent_anchor = anchor;
3005        $parent_spin   = spin;
3006        $parent_orient = orient;
3007        $parent_geom   = geom;
3008        $parent_size   = _attach_geom_size(geom);
3009        $attach_to   = undef;
3010        if (_is_shown()) {
3011            _color($color) {
3012                linear_extrude(height=h, center=true)
3013                    _text(
3014                        text=text, size=size, font=font,
3015                        halign=ha, valign=va, spacing=spacing,
3016                        direction=direction, language=language,
3017                        script=script
3018                    );
3019            }
3020        }
3021    }
3022}
3023
3024
3025// This could be replaced with _cut_to_seg_u_form
3026function _cut_interp(pathcut, path, data) =
3027  [for(entry=pathcut)
3028    let(
3029       a = path[entry[1]-1],
3030        b = path[entry[1]],
3031        c = entry[0],
3032        i = max_index(v_abs(b-a)),
3033        factor = (c[i]-a[i])/(b[i]-a[i])
3034    )
3035    (1-factor)*data[entry[1]-1]+ factor * data[entry[1]]
3036  ];
3037
3038
3039// Module: path_text()
3040// Synopsis: Creates 2d or 3d text placed along a path.
3041// SynTags: Geom
3042// Topics: Text, Paths, Paths (2D), Paths (3D), Path Generators, Path Generators (2D)
3043// See Also, text(), text2d()
3044// Usage:
3045//   path_text(path, text, [size], [thickness], [font], [lettersize=], [offset=], [reverse=], [normal=], [top=], [textmetrics=], [kern=])
3046// Description:
3047//   Place the text letter by letter onto the specified path using textmetrics (if available and requested)
3048//   or user specified letter spacing.  The path can be 2D or 3D.  In 2D the text appears along the path with letters upright
3049//   as determined by the path direction.  In 3D by default letters are positioned on the tangent line to the path with the path normal
3050//   pointing toward the reader.  The path normal points away from the center of curvature (the opposite of the normal produced
3051//   by path_normals()).  Note that this means that if the center of curvature switches sides the text will flip upside down.
3052//   If you want text on such a path you must supply your own normal or top vector.
3053//   .
3054//   Text appears starting at the beginning of the path, so if the 3D path moves right to left
3055//   then a left-to-right reading language will display in the wrong order. (For a 2D path text will appear upside down.)
3056//   The text for a 3D path appears positioned to be read from "outside" of the curve (from a point on the other side of the
3057//   curve from the center of curvature).  If you need the text to read properly from the inside, you can set reverse to
3058//   true to flip the text, or supply your own normal.
3059//   .
3060//   If you do not have the experimental textmetrics feature enabled then you must specify the space for the letters
3061//   using lettersize, which can be a scalar or array.  You will have the easiest time getting good results by using
3062//   a monospace font such as Courier.  Note that even with text metrics, spacing may be different because path_text()
3063//   doesn't do kerning to adjust positions of individual glyphs.  Also if your font has ligatures they won't be used.
3064//   .
3065//   By default letters appear centered on the path.  The offset can be specified to shift letters toward the reader (in
3066//   the direction of the normal).
3067//   .
3068//   You can specify your own normal by setting `normal` to a direction or a list of directions.  Your normal vector should
3069//   point toward the reader.  You can also specify
3070//   top, which directs the top of the letters in a desired direction.  If you specify your own directions and they
3071//   are not perpendicular to the path then the direction you specify will take priority and the
3072//   letters will not rest on the tangent line of the path.  Note that the normal or top directions that you
3073//   specify must not be parallel to the path.
3074//   .
3075//   Historically fonts were specified by their "body size", the height of the metal body
3076//   on which the glyphs were cast.  This means the size was an upper bound on the size
3077//   of the font glyphs, not a direct measurement of their size.  In digital typesetting,
3078//   the metal body is replaced by an invisible box, the em square, whose side length is
3079//   defined to be the font's size.  The glyphs can be contained in that square, or they
3080//   can extend beyond it, depending on the choices made by the font designer.  As a
3081//   result, the meaning of font size varies between fonts: two fonts at the "same" size
3082//   can differ significantly in the actual size of their characters.  Typographers
3083//   customarily specify the size in the units of "points".  A point is 1/72 inch.  In
3084//   OpenSCAD, you specify the size in OpenSCAD units (often treated as millimeters for 3d
3085//   printing), so if you want points you will need to perform a suitable unit conversion.
3086//   In addition, the OpenSCAD font system has a bug: if you specify size=s you will
3087//   instead get a font whose size is s/0.72.  For many fonts this means the size of
3088//   capital letters will be approximately equal to s, because it is common for fonts to
3089//   use about 70% of their height for the ascenders in the font.  To get the customary
3090//   font size, you should multiply your desired size by 0.72.
3091//   .
3092//   To find the fonts that you have available in your OpenSCAD installation,
3093//   go to the Help menu and select "Font List".
3094// Arguments:
3095//   path = path to place the text on
3096//   text = text to create
3097//   size = The font will be created at this size divided by 0.72.   
3098//   thickness / h / height = thickness of letters (not allowed for 2D path)
3099//   font = font to use.  Default: "Liberation Sans"
3100//   ---
3101//   lettersize = scalar or array giving size of letters
3102//   center = center text on the path instead of starting at the first point.  Default: false
3103//   offset = distance to shift letters "up" (towards the reader).  Not allowed for 2D path.  Default: 0
3104//   normal = direction or list of directions pointing towards the reader of the text.  Not allowed for 2D path.
3105//   top = direction or list of directions pointing toward the top of the text
3106//   reverse = reverse the letters if true.  Not allowed for 2D path.  Default: false
3107//   textmetrics = if set to true and lettersize is not given then use the experimental textmetrics feature.  You must be running a dev snapshot that includes this feature and have the feature turned on in your preferences.  Default: false
3108//   valign = align text to the path using "top", "bottom", "center" or "baseline".  You can also adjust position with a numerical offset as in "top-5" or "bottom+2".  This only works with textmetrics enabled.  You can give a simple numerical offset, which will be relative to the baseline and works even without textmetrics.  Default: "baseline"
3109//   kern = scalar or array giving spacing adjusments between each letter.  If it's an array it should have one less entry than the text string.  Default: 0
3110//   language = text language, passed to OpenSCAD `text()`.  Default: "en"
3111//   script = text script, passed to OpenSCAD `text()`.  Default: "latin" 
3112// Example(3D,NoScales):  The examples use Courier, a monospaced font.  The width is 1/1.2 times the specified size for this font.  This text could wrap around a cylinder.
3113//   path = path3d(arc(100, r=25, angle=[245, 370]));
3114//   color("red")stroke(path, width=.3);
3115//   path_text(path, "Example text", font="Courier", size=5, lettersize = 5/1.2);
3116// Example(3D,NoScales): By setting the normal to UP we can get text that lies flat, for writing around the edge of a disk:
3117//   path = path3d(arc(100, r=25, angle=[245, 370]));
3118//   color("red")stroke(path, width=.3);
3119//   path_text(path, "Example text", font="Courier", size=5, lettersize = 5/1.2, normal=UP);
3120// Example(3D,NoScales):  If we want text that reads from the other side we can use reverse.  Note we have to reverse the direction of the path and also set the reverse option.
3121//   path = reverse(path3d(arc(100, r=25, angle=[65, 190])));
3122//   color("red")stroke(path, width=.3);
3123//   path_text(path, "Example text", font="Courier", size=5, lettersize = 5/1.2, reverse=true);
3124// Example(3D,Med,NoScales): text debossed onto a cylinder in a spiral.  The text is 1 unit deep because it is half in, half out.
3125//   text = ("A long text example to wrap around a cylinder, possibly for a few times.");
3126//   L = 5*len(text);
3127//   maxang = 360*L/(PI*50);
3128//   spiral = [for(a=[0:1:maxang]) [25*cos(a), 25*sin(a), 10-30/maxang*a]];
3129//   difference(){
3130//     cyl(d=50, l=50, $fn=120);
3131//     path_text(spiral, text, size=5, lettersize=5/1.2, font="Courier", thickness=2);
3132//   }
3133// Example(3D,Med,NoScales): Same example but text embossed.  Make sure you have enough depth for the letters to fully overlap the object.
3134//   text = ("A long text example to wrap around a cylinder, possibly for a few times.");
3135//   L = 5*len(text);
3136//   maxang = 360*L/(PI*50);
3137//   spiral = [for(a=[0:1:maxang]) [25*cos(a), 25*sin(a), 10-30/maxang*a]];
3138//   cyl(d=50, l=50, $fn=120);
3139//   path_text(spiral, text, size=5, lettersize=5/1.2, font="Courier", thickness=2);
3140// Example(3D,NoScales): Here the text baseline sits on the path.  (Note the default orientation makes text readable from below, so we specify the normal.)
3141//   path = arc(100, points = [[-20, 0, 20], [0,0,5], [20,0,20]]);
3142//   color("red")stroke(path,width=.2);
3143//   path_text(path, "Example Text", size=5, lettersize=5/1.2, font="Courier", normal=FRONT);
3144// Example(3D,NoScales): If we use top to orient the text upward, the text baseline is no longer aligned with the path.
3145//   path = arc(100, points = [[-20, 0, 20], [0,0,5], [20,0,20]]);
3146//   color("red")stroke(path,width=.2);
3147//   path_text(path, "Example Text", size=5, lettersize=5/1.2, font="Courier", top=UP);
3148// Example(3D,Med,NoScales): This sine wave wrapped around the cylinder has a twisting normal that produces wild letter layout.  We fix it with a custom normal which is different at every path point.
3149//   path = [for(theta = [0:360]) [25*cos(theta), 25*sin(theta), 4*cos(theta*4)]];
3150//   normal = [for(theta = [0:360]) [cos(theta), sin(theta),0]];
3151//   zrot(-120)
3152//   difference(){
3153//     cyl(r=25, h=20, $fn=120);
3154//     path_text(path, "A sine wave wiggles", font="Courier", lettersize=5/1.2, size=5, normal=normal);
3155//   }
3156// Example(3D,Med,NoScales): The path center of curvature changes, and the text flips.
3157//   path =  zrot(-120,p=path3d( concat(arc(100, r=25, angle=[0,90]), back(50,p=arc(100, r=25, angle=[268, 180])))));
3158//   color("red")stroke(path,width=.2);
3159//   path_text(path, "A shorter example",  size=5, lettersize=5/1.2, font="Courier", thickness=2);
3160// Example(3D,Med,NoScales): We can fix it with top:
3161//   path =  zrot(-120,p=path3d( concat(arc(100, r=25, angle=[0,90]), back(50,p=arc(100, r=25, angle=[268, 180])))));
3162//   color("red")stroke(path,width=.2);
3163//   path_text(path, "A shorter example",  size=5, lettersize=5/1.2, font="Courier", thickness=2, top=UP);
3164// Example(2D,NoScales): With a 2D path instead of 3D there's no ambiguity about direction and it works by default:
3165//   path =  zrot(-120,p=concat(arc(100, r=25, angle=[0,90]), back(50,p=arc(100, r=25, angle=[268, 180]))));
3166//   color("red")stroke(path,width=.2);
3167//   path_text(path, "A shorter example",  size=5, lettersize=5/1.2, font="Courier");
3168// Example(3D,NoScales): The kern parameter lets you adjust the letter spacing either with a uniform value for each letter, or with an array to make adjustments throughout the text.  Here we show a case where adding some extra space gives a better look in a tight circle.  When textmetrics are off, `lettersize` can do this job, but with textmetrics, you'll need to use `kern` to make adjustments relative to the text metric sizes.
3169//   path = path3d(arc(100, r=12, angle=[150, 450]));
3170//   color("red")stroke(path, width=.3);
3171//   kern = [1,1.2,1,1,.3,-.2,1,0,.8,1,1.1];
3172//   path_text(path, "Example text", font="Courier", size=5, lettersize = 5/1.2, kern=kern, normal=UP);
3173
3174module path_text(path, text, font, size, thickness, lettersize, offset=0, reverse=false, normal, top, center=false,
3175                 textmetrics=false, kern=0, height,h, valign="baseline", language, script)
3176{
3177  no_children($children);
3178  dummy2=assert(is_path(path,[2,3]),"Must supply a 2d or 3d path")
3179         assert(num_defined([normal,top])<=1, "Cannot define both \"normal\" and \"top\"")
3180         assert(all_positive([size]), "Must give positive text size");
3181  dim = len(path[0]);
3182  normalok = is_undef(normal) || is_vector(normal,3) || (is_path(normal,3) && len(normal)==len(path));
3183  topok = is_undef(top) || is_vector(top,dim) || (dim==2 && is_vector(top,3) && top[2]==0)
3184                        || (is_path(top,dim) && len(top)==len(path));
3185  dummy4 = assert(dim==3 || !any_defined([thickness,h,height]), "Cannot give a thickness or height with 2d path")
3186           assert(dim==3 || !reverse, "Reverse not allowed with 2d path")
3187           assert(dim==3 || offset==0, "Cannot give offset with 2d path")
3188           assert(dim==3 || is_undef(normal), "Cannot define \"normal\" for a 2d path, only \"top\"")
3189           assert(normalok,"\"normal\" must be a vector or path compatible with the given path")
3190           assert(topok,"\"top\" must be a vector or path compatible with the given path");
3191  thickness = one_defined([thickness,h,height],"thickness,h,height",dflt=1);
3192  normal = is_vector(normal) ? repeat(normal, len(path))
3193         : is_def(normal) ? normal
3194         : undef;
3195
3196  top = is_vector(top) ? repeat(dim==2?point2d(top):top, len(path))
3197         : is_def(top) ? top
3198         : undef;
3199
3200  kern = force_list(kern, len(text)-1);
3201  dummy3 = assert(is_list(kern) && len(kern)==len(text)-1, "kern must be a scalar or list whose length is len(text)-1");
3202
3203  lsize = is_def(lettersize) ? force_list(lettersize, len(text))
3204        : textmetrics ? [for(letter=text) let(t=textmetrics(letter, font=font, size=size)) t.advance[0]]
3205        : assert(false, "textmetrics disabled: Must specify letter size");
3206  lcenter = convolve(lsize,[1,1]/2)+[0,each kern,0] ;
3207  textlength = sum(lsize)+sum(kern);
3208
3209  ascent = !textmetrics ? undef
3210         : textmetrics(text, font=font, size=size).ascent;
3211  descent = !textmetrics ? undef
3212          : textmetrics(text, font=font, size=size).descent;
3213
3214  vadjustment = is_num(valign) ? -valign
3215              : !textmetrics ? assert(valign=="baseline","valign requires textmetrics support") 0
3216              : let(
3217                     table = [
3218                              ["baseline", 0],
3219                              ["top", -ascent],
3220                              ["bottom", descent],
3221                              ["center", (descent-ascent)/2]
3222                             ],
3223                     match = [for(i=idx(table)) if (starts_with(valign,table[i][0])) i]
3224                )
3225                assert(len(match)==1, "Invalid valign value")
3226                table[match[0]][1] - parse_num(substr(valign,len(table[match[0]][0])));
3227
3228  dummy1 = assert(textlength<=path_length(path),"Path is too short for the text");
3229
3230  start = center ? (path_length(path) - textlength)/2 : 0;
3231   
3232  pts = path_cut_points(path, add_scalar(cumsum(lcenter),start), direction=true);
3233
3234  usernorm = is_def(normal);
3235  usetop = is_def(top);
3236  normpts = is_undef(normal) ? (reverse?1:-1)*column(pts,3) : _cut_interp(pts,path, normal);
3237  toppts = is_undef(top) ? undef : _cut_interp(pts,path,top);
3238  attachable(){
3239    for (i = idx(text)) {
3240      tangent = pts[i][2];
3241      checks =
3242          assert(!usetop || !approx(tangent*toppts[i],norm(top[i])*norm(tangent)),
3243                 str("Specified top direction parallel to path at character ",i))
3244          assert(usetop || !approx(tangent*normpts[i],norm(normpts[i])*norm(tangent)),
3245                 str("Specified normal direction parallel to path at character ",i));
3246      adjustment = usetop ?  (tangent*toppts[i])*toppts[i]/(toppts[i]*toppts[i])
3247                 : usernorm ?  (tangent*normpts[i])*normpts[i]/(normpts[i]*normpts[i])
3248                 : [0,0,0];
3249      move(pts[i][0]) {
3250        if (dim==3) {
3251          frame_map(
3252            x=tangent-adjustment,
3253            z=usetop ? undef : normpts[i],
3254            y=usetop ? toppts[i] : undef
3255          ) up(offset-thickness/2) {
3256            linear_extrude(height=thickness)
3257              back(vadjustment)
3258              {
3259              left(lsize[i]/2)
3260                text(text[i], font=font, size=size, language=language, script=script);
3261              }
3262          }
3263        } else {
3264            frame_map(
3265              x=point3d(tangent-adjustment),
3266              y=point3d(usetop ? toppts[i] : -normpts[i])
3267            ) left(lsize[0]/2) {
3268                text(text[i], font=font, size=size, language=language, script=script);
3269            }
3270        }
3271      }
3272    }
3273    union();
3274  }
3275}
3276
3277
3278
3279// Section: Miscellaneous
3280
3281
3282// Module: fillet()
3283// Synopsis: Creates a smooth fillet between two faces.
3284// SynTags: Geom, VNF
3285// Topics: Shapes (3D), Attachable
3286// See Also: mask2d_roundover()
3287// Description:
3288//   Creates a shape that can be unioned into a concave joint between two faces, to fillet them.
3289//   Note that this module is the same as {{rounding_edge_mask()}}, except that it does not
3290//   apply the default "remove" tag.  
3291//
3292// Usage: 
3293//   fillet(l|h=|length=|height=, r|d=, [ang=], [excess=]) [ATTACHMENTS];
3294//   fillet(l|h=|length=|height=, r1=|d1=, r2=|d2=, [ang=], [excess=]) [ATTACHMENTS];
3295//
3296// Arguments:
3297//   l / length / h / height = Length of edge to fillet.
3298//   r = Radius of fillet.
3299//   ang = Angle between faces to fillet.
3300//   excess = Overlap size for unioning with faces.
3301//   ---
3302//   d = Diameter of fillet.
3303//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `FRONT+LEFT`
3304//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
3305//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
3306//
3307// Example:
3308//   union() {
3309//     translate([0,2,-4])
3310//       cube([20, 4, 24], anchor=BOTTOM);
3311//     translate([0,-10,-4])
3312//       cube([20, 20, 4], anchor=BOTTOM);
3313//     color("green")
3314//       fillet(
3315//         l=20, r=10,
3316//         spin=180, orient=RIGHT
3317//       );
3318//   }
3319//
3320// Examples:
3321//   fillet(l=10, r=20, ang=60);
3322//   fillet(l=10, r=20, ang=90);
3323//   fillet(l=10, r=20, ang=120);
3324//
3325// Example: Using with Attachments
3326//   cube(50,center=true) {
3327//     position(FRONT+LEFT)
3328//       fillet(l=50, r=10, spin=-90);
3329//     position(BOT+FRONT)
3330//       fillet(l=50, r=10, spin=180, orient=RIGHT);
3331//   }
3332// Example: 
3333//   cuboid(50){
3334//     align(TOP,RIGHT,inset=10) fillet(l=50,r=10,orient=FWD);
3335//     align(TOP,RIGHT,inset=20) cuboid([4,50,20],anchor=BOT);
3336//   }   
3337
3338module interior_fillet(l=1.0, r, ang=90, overlap=0.01, d, length, h, height, anchor=CENTER, spin=0, orient=UP)
3339{
3340    deprecate("fillet");
3341    fillet(l,r,ang,overlap,d,length,h,height,anchor,spin,orient);
3342}
3343
3344
3345function fillet(l, r, ang, r1, r2, d, d1, d2, excess=0.1, anchor=CENTER, spin=0, orient=UP, h,height,length) = no_function("fillet");
3346module fillet(l, r, ang=90, r1, r2, excess=0.01, d1, d2,d,length, h, height, anchor=CENTER, spin=0, orient=UP)
3347{
3348  rounding_edge_mask(l=l, r1=r1, r2=r2, ang=ang, excess=excess, d1=d1, d2=d2,d=d,r=r,length=length, h=h, height=height,
3349                     anchor=anchor, spin=spin, orient=orient, _remove_tag=false)
3350    children();
3351}  
3352
3353
3354// Function&Module: heightfield()
3355// Synopsis: Generates a 3D surface from a 2D grid of values.
3356// SynTags: Geom, VNF
3357// Topics: Textures, Heightfield
3358// See Also: cylindrical_heightfield()
3359// Usage: As Module
3360//   heightfield(data, [size], [bottom], [maxz], [xrange], [yrange], [style], [convexity], ...) [ATTACHMENTS];
3361// Usage: As Function
3362//   vnf = heightfield(data, [size], [bottom], [maxz], [xrange], [yrange], [style], ...);
3363// Description:
3364//   Given a regular rectangular 2D grid of scalar values, or a function literal, generates a 3D
3365//   surface where the height at any given point is the scalar value for that position.
3366//   One script to convert a grayscale image to a heightfield array in a .scad file can be found at:
3367//   https://raw.githubusercontent.com/BelfrySCAD/BOSL2/master/scripts/img2scad.py
3368//   The bottom value defines a planar base for the resulting shape and it must be strictly less than
3369//   the model data to produce valid geometry, so data which is too small is set to 0.1 units above the bottom value. 
3370// Arguments:
3371//   data = This is either the 2D rectangular array of heights, or a function literal that takes X and Y arguments.
3372//   size = The [X,Y] size of the surface to create.  If given as a scalar, use it for both X and Y sizes. Default: `[100,100]`
3373//   bottom = The Z coordinate for the bottom of the heightfield object to create.  Any heights lower than this will be truncated to very slightly (0.1) above this height.  Default: -20
3374//   maxz = The maximum height to model.  Truncates anything taller to this height.  Set to INF for no truncation.  Default: 100
3375//   xrange = A range of values to iterate X over when calculating a surface from a function literal.  Default: [-1 : 0.01 : 1]
3376//   yrange = A range of values to iterate Y over when calculating a surface from a function literal.  Default: [-1 : 0.01 : 1]
3377//   style = The style of subdividing the quads into faces.  Valid options are "default", "alt", and "quincunx".  Default: "default"
3378//   ---
3379//   convexity = Max number of times a line could intersect a wall of the surface being formed. Module only.  Default: 10
3380//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
3381//   spin = Rotate this many degrees around the Z axis.  See [spin](attachments.scad#subsection-spin).  Default: `0`
3382//   orient = Vector to rotate top towards.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
3383// Example:
3384//   heightfield(size=[100,100], bottom=-20, data=[
3385//       for (y=[-180:4:180]) [
3386//           for(x=[-180:4:180])
3387//           10*cos(3*norm([x,y]))
3388//       ]
3389//   ]);
3390// Example:
3391//   intersection() {
3392//       heightfield(size=[100,100], data=[
3393//           for (y=[-180:5:180]) [
3394//               for(x=[-180:5:180])
3395//               10+5*cos(3*x)*sin(3*y)
3396//           ]
3397//       ]);
3398//       cylinder(h=50,d=100);
3399//   }
3400// Example: Heightfield by Function
3401//   fn = function (x,y) 10*sin(x*360)*cos(y*360);
3402//   heightfield(size=[100,100], data=fn);
3403// Example: Heightfield by Function, with Specific Ranges
3404//   fn = function (x,y) 2*cos(5*norm([x,y]));
3405//   heightfield(
3406//       size=[100,100], bottom=-20, data=fn,
3407//       xrange=[-180:2:180], yrange=[-180:2:180]
3408//   );
3409
3410module heightfield(data, size=[100,100], bottom=-20, maxz=100, xrange=[-1:0.04:1], yrange=[-1:0.04:1], style="default", convexity=10, anchor=CENTER, spin=0, orient=UP)
3411{
3412    size = is_num(size)? [size,size] : point2d(size);
3413    vnf = heightfield(data=data, size=size, xrange=xrange, yrange=yrange, bottom=bottom, maxz=maxz, style=style);
3414    attachable(anchor,spin,orient, vnf=vnf) {
3415        vnf_polyhedron(vnf, convexity=convexity);
3416        children();
3417    }
3418}
3419
3420
3421function heightfield(data, size=[100,100], bottom=-20, maxz=100, xrange=[-1:0.04:1], yrange=[-1:0.04:1], style="default", anchor=CENTER, spin=0, orient=UP) =
3422    assert(is_list(data) || is_function(data))
3423    let(
3424        size = is_num(size)? [size,size] : point2d(size),
3425        xvals = is_list(data)
3426          ? [for (i=idx(data[0])) i]
3427          : assert(is_list(xrange)||is_range(xrange)) [for (x=xrange) x],
3428        yvals = is_list(data)
3429          ? [for (i=idx(data)) i]
3430          : assert(is_list(yrange)||is_range(yrange)) [for (y=yrange) y],
3431        xcnt = len(xvals),
3432        minx = min(xvals),
3433        maxx = max(xvals),
3434        ycnt = len(yvals),
3435        miny = min(yvals),
3436        maxy = max(yvals),
3437        verts = is_list(data) ? [
3438                for (y = [0:1:ycnt-1]) [
3439                    for (x = [0:1:xcnt-1]) [
3440                        size.x * (x/(xcnt-1)-0.5),
3441                        size.y * (y/(ycnt-1)-0.5),
3442                        min(max(data[y][x],bottom+0.1),maxz)
3443                    ]
3444                ]
3445            ] : [
3446                for (y = yrange) [
3447                    for (x = xrange) let(
3448                        z = data(x,y)
3449                    ) [
3450                        size.x * ((x-minx)/(maxx-minx)-0.5),
3451                        size.y * ((y-miny)/(maxy-miny)-0.5),
3452                        min(maxz, max(bottom+0.1, default(z,0)))
3453                    ]
3454                ]
3455            ],
3456        vnf = vnf_join([
3457            vnf_vertex_array(verts, style=style, reverse=true),
3458            vnf_vertex_array([
3459                verts[0],
3460                [for (v=verts[0]) [v.x, v.y, bottom]],
3461            ]),
3462            vnf_vertex_array([
3463                [for (v=verts[ycnt-1]) [v.x, v.y, bottom]],
3464                verts[ycnt-1],
3465            ]),
3466            vnf_vertex_array([
3467                [for (r=verts) let(v=r[0]) [v.x, v.y, bottom]],
3468                [for (r=verts) let(v=r[0]) v],
3469            ]),
3470            vnf_vertex_array([
3471                [for (r=verts) let(v=r[xcnt-1]) v],
3472                [for (r=verts) let(v=r[xcnt-1]) [v.x, v.y, bottom]],
3473            ]),
3474            vnf_vertex_array([
3475                [
3476                    for (v=verts[0]) [v.x, v.y, bottom],
3477                    for (r=verts) let(v=r[xcnt-1]) [v.x, v.y, bottom],
3478                ], [
3479                    for (r=verts) let(v=r[0]) [v.x, v.y, bottom],
3480                    for (v=verts[ycnt-1]) [v.x, v.y, bottom],
3481                ]
3482            ])
3483        ])
3484    ) reorient(anchor,spin,orient, vnf=vnf, p=vnf);
3485
3486
3487// Function&Module: cylindrical_heightfield()
3488// Synopsis: Generates a cylindrical 3d surface from a 2D grid of values.
3489// SynTags: Geom, VNF
3490// Topics: Extrusion, Textures, Knurling, Heightfield
3491// See Also: heightfield()
3492// Usage: As Function
3493//   vnf = cylindrical_heightfield(data, l|length=|h=|height=, r|d=, [base=], [transpose=], [aspect=]);
3494// Usage: As Module
3495//   cylindrical_heightfield(data, l|length=|h=|height=, r|d=, [base=], [transpose=], [aspect=]) [ATTACHMENTS];
3496// Description:
3497//   Given a regular rectangular 2D grid of scalar values, or a function literal of signature (x,y), generates
3498//   a cylindrical 3D surface where the height at any given point above the radius `r=`, is the scalar value
3499//   for that position.
3500//   One script to convert a grayscale image to a heightfield array in a .scad file can be found at:
3501//   https://raw.githubusercontent.com/BelfrySCAD/BOSL2/master/scripts/img2scad.py
3502// Arguments:
3503//   data = This is either the 2D rectangular array of heights, or a function literal of signature `(x, y)`.
3504//   l / length / h / height = The length of the cylinder to wrap around.
3505//   r = The radius of the cylinder to wrap around.
3506//   ---
3507//   r1 = The radius of the bottom of the cylinder to wrap around.
3508//   r2 = The radius of the top of the cylinder to wrap around.
3509//   d = The diameter of the cylinder to wrap around.
3510//   d1 = The diameter of the bottom of the cylinder to wrap around.
3511//   d2 = The diameter of the top of the cylinder to wrap around.
3512//   base = The radius for the bottom of the heightfield object to create.  Any heights smaller than this will be truncated to very slightly above this height.  Default: -20
3513//   transpose = If true, swaps the radial and length axes of the data.  Default: false
3514//   aspect = The aspect ratio of the generated heightfield at the surface of the cylinder.  Default: 1
3515//   xrange = A range of values to iterate X over when calculating a surface from a function literal.  Default: [-1 : 0.01 : 1]
3516//   yrange = A range of values to iterate Y over when calculating a surface from a function literal.  Default: [-1 : 0.01 : 1]
3517//   maxh = The maximum height above the radius to model.  Truncates anything taller to this height.  Default: 99
3518//   style = The style of subdividing the quads into faces.  Valid options are "default", "alt", and "quincunx".  Default: "default"
3519//   convexity = Max number of times a line could intersect a wall of the surface being formed. Module only.  Default: 10
3520//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
3521//   spin = Rotate this many degrees around the Z axis.  See [spin](attachments.scad#subsection-spin).  Default: `0`
3522//   orient = Vector to rotate top towards.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
3523// Example(VPD=400;VPR=[55,0,150]):
3524//   cylindrical_heightfield(l=100, r=30, base=5, data=[
3525//       for (y=[-180:4:180]) [
3526//           for(x=[-180:4:180])
3527//           5*cos(5*norm([x,y]))+5
3528//       ]
3529//   ]);
3530// Example(VPD=400;VPR=[55,0,150]):
3531//   cylindrical_heightfield(l=100, r1=60, r2=30, base=5, data=[
3532//       for (y=[-180:4:180]) [
3533//           for(x=[-180:4:180])
3534//           5*cos(5*norm([x,y]))+5
3535//       ]
3536//   ]);
3537// Example(VPD=400;VPR=[55,0,150]): Heightfield by Function
3538//   fn = function (x,y) 5*sin(x*360)*cos(y*360)+5;
3539//   cylindrical_heightfield(l=100, r=30, data=fn);
3540// Example(VPD=400;VPR=[55,0,150]): Heightfield by Function, with Specific Ranges
3541//   fn = function (x,y) 2*cos(5*norm([x,y]));
3542//   cylindrical_heightfield(
3543//       l=100, r=30, base=5, data=fn,
3544//       xrange=[-180:2:180], yrange=[-180:2:180]
3545//   );
3546
3547function cylindrical_heightfield(
3548    data, l, r, base=1,
3549    transpose=false, aspect=1,
3550    style="min_edge", maxh=99,
3551    xrange=[-1:0.01:1],
3552    yrange=[-1:0.01:1],
3553    r1, r2, d, d1, d2, h, height, length, 
3554    anchor=CTR, spin=0, orient=UP
3555) =
3556    let(
3557        l = one_defined([l, h, height, length], "l,h,height,l"),
3558        r1 = get_radius(r1=r1, r=r, d1=d1, d=d),
3559        r2 = get_radius(r1=r2, r=r, d1=d2, d=d)
3560    )
3561    assert(is_finite(l) && l>0, "Must supply one of l=, h=, or height= as a finite positive number.")
3562    assert(is_finite(r1) && r1>0, "Must supply one of r=, r1=, d=, or d1= as a finite positive number.")
3563    assert(is_finite(r2) && r2>0, "Must supply one of r=, r2=, d=, or d2= as a finite positive number.")
3564    assert(is_finite(base) && base>0, "Must supply base= as a finite positive number.")
3565    assert(is_matrix(data)||is_function(data), "data= must be a function literal, or contain a 2D array of numbers.")
3566    let(
3567        xvals = is_list(data)? [for (x = idx(data[0])) x] :
3568            is_range(xrange)? [for (x = xrange) x] :
3569            assert(false, "xrange= must be given as a range if data= is a function literal."),
3570        yvals = is_list(data)? [for (y = idx(data)) y] :
3571            is_range(yrange)? [for (y = yrange) y] :
3572            assert(false, "yrange= must be given as a range if data= is a function literal."),
3573        xlen = len(xvals),
3574        ylen = len(yvals),
3575        stepy = l / (ylen-1),
3576        stepx = stepy * aspect,
3577        maxr = max(r1,r2),
3578        circ = 2 * PI * maxr,
3579        astep = 360 / circ * stepx,
3580        arc = astep * (xlen-1),
3581        bsteps = round(segs(maxr-base) * arc / 360),
3582        bstep = arc / bsteps
3583    )
3584    assert(stepx*xlen <= circ, str("heightfield (",xlen," x ",ylen,") needs a radius of at least ",maxr*stepx*xlen/circ))
3585    let(
3586        verts = [
3587            for (yi = idx(yvals)) let(
3588                z = yi * stepy - l/2,
3589                rr = lerp(r1, r2, yi/(ylen-1))
3590            ) [
3591                cylindrical_to_xyz(rr-base, -arc/2, z),
3592                for (xi = idx(xvals)) let( a = xi*astep )
3593                    let(
3594                        rad = transpose? (
3595                                is_list(data)? data[xi][yi] : data(yvals[yi],xvals[xi])
3596                            ) : (
3597                                is_list(data)? data[yi][xi] : data(xvals[xi],yvals[yi])
3598                            ),
3599                        rad2 = constrain(rad, 0.01-base, maxh)
3600                    )
3601                    cylindrical_to_xyz(rr+rad2, a-arc/2, z),
3602                cylindrical_to_xyz(rr-base, arc/2, z),
3603                for (b = [1:1:bsteps-1]) let( a = arc/2-b*bstep )
3604                    cylindrical_to_xyz((z>0?r2:r1)-base, a, l/2*(z>0?1:-1)),
3605            ]
3606        ],
3607        vnf = vnf_vertex_array(verts, caps=true, col_wrap=true, reverse=true, style=style)
3608    ) reorient(anchor,spin,orient, r1=r1, r2=r2, l=l, p=vnf);
3609
3610
3611module cylindrical_heightfield(
3612    data, l, r, base=1,
3613    transpose=false, aspect=1,
3614    style="min_edge", convexity=10,
3615    xrange=[-1:0.01:1], yrange=[-1:0.01:1],
3616    maxh=99, r1, r2, d, d1, d2, h, height, length,
3617    anchor=CTR, spin=0, orient=UP
3618) {
3619    l = one_defined([l, h, height, length], "l,h,height,length");
3620    r1 = get_radius(r1=r1, r=r, d1=d1, d=d);
3621    r2 = get_radius(r1=r2, r=r, d1=d2, d=d);
3622    vnf = cylindrical_heightfield(
3623        data, l=l, r1=r1, r2=r2, base=base,
3624        xrange=xrange, yrange=yrange,
3625        maxh=maxh, transpose=transpose,
3626        aspect=aspect, style=style
3627    );
3628    attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) {
3629        vnf_polyhedron(vnf, convexity=convexity);
3630        children();
3631    }
3632}
3633
3634
3635// Module: ruler()
3636// Synopsis: Creates a ruler.
3637// SynTags: Geom
3638// Topics: Distance
3639// Usage:
3640//   ruler(length, width, [thickness=], [depth=], [labels=], [pipscale=], [maxscale=], [colors=], [alpha=], [unit=], [inch=]) [ATTACHMENTS];
3641// Description:
3642//   Creates an attachable ruler for checking dimensions of the model.  The rule appears only in preview mode (F5) and is not displayed
3643//   when the model is rendered (F6).  
3644// Arguments:
3645//   length = length of the ruler.  Default 100
3646//   width = width of the ruler.  Default: size of the largest unit division
3647//   ---
3648//   thickness = thickness of the ruler. Default: 1
3649//   depth = the depth of mark subdivisions. Default: 3
3650//   labels = draw numeric labels for depths where labels are larger than 1.  Default: false
3651//   pipscale = width scale of the pips relative to the next size up.  Default: 1/3
3652//   maxscale = log10 of the maximum width divisions to display.  Default: based on input length
3653//   colors = colors to use for the ruler, a list of two values.  Default: `["black","white"]`
3654//   alpha = transparency value.  Default: 1.0
3655//   unit = unit to mark.  Scales the ruler marks to a different length.  Default: 1
3656//   inch = set to true for a ruler scaled to inches (assuming base dimension is mm).  Default: false
3657//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `LEFT+BACK+TOP`
3658//   spin = Rotate this many degrees around the Z axis.  See [spin](attachments.scad#subsection-spin).  Default: `0`
3659//   orient = Vector to rotate top towards.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
3660// Examples(2D,Big):
3661//   ruler(100,depth=3);
3662//   ruler(100,depth=3,labels=true);
3663//   ruler(27);
3664//   ruler(27,maxscale=0);
3665//   ruler(100,pipscale=3/4,depth=2);
3666//   ruler(100,width=2,depth=2);
3667// Example(2D,Big):  Metric vs Imperial
3668//   ruler(12,width=50,inch=true,labels=true,maxscale=0);
3669//   fwd(50)ruler(300,width=50,labels=true);
3670
3671module ruler(length=100, width, thickness=1, depth=3, labels=false, pipscale=1/3, maxscale,
3672             colors=["black","white"], alpha=1.0, unit=1, inch=false, anchor=LEFT+BACK+TOP, spin=0, orient=UP)
3673{
3674    if ($preview){
3675        checks =
3676            assert(depth<=5, "Cannot render scales smaller than depth=5")
3677            assert(len(colors)==2, "colors must contain a list of exactly two colors.");
3678        length = inch ? INCH * length : length;
3679        unit = inch ? INCH*unit : unit;
3680        maxscale = is_def(maxscale)? maxscale : floor(log(length/unit-EPSILON));
3681        scales = unit * [for(logsize = [maxscale:-1:maxscale-depth+1]) pow(10,logsize)];
3682        widthfactor = (1-pipscale) / (1-pow(pipscale,depth));
3683        width = default(width, scales[0]);
3684        widths = width * widthfactor * [for(logsize = [0:-1:-depth+1]) pow(pipscale,-logsize)];
3685        offsets = concat([0],cumsum(widths));
3686        attachable(anchor,spin,orient, size=[length,width,thickness]) {
3687            translate([-length/2, -width/2, 0])
3688            for(i=[0:1:len(scales)-1]) {
3689                count = ceil(length/scales[i]);
3690                fontsize = 0.5*min(widths[i], scales[i]/ceil(log(count*scales[i]/unit)));
3691                back(offsets[i]) {
3692                    xcopies(scales[i], n=count, sp=[0,0,0]) union() {
3693                        actlen = ($idx<count-1) || approx(length%scales[i],0) ? scales[i] : length % scales[i];
3694                        color(colors[$idx%2], alpha=alpha) {
3695                            w = i>0 ? quantup(widths[i],1/1024) : widths[i];    // What is the i>0 test supposed to do here?
3696                            cube([quantup(actlen,1/1024),quantup(w,1/1024),thickness], anchor=FRONT+LEFT);
3697                        }
3698                        mark =
3699                            i == 0 && $idx % 10 == 0 && $idx != 0 ? 0 :
3700                            i == 0 && $idx % 10 == 9 && $idx != count-1 ? 1 :
3701                            $idx % 10 == 4 ? 1 :
3702                            $idx % 10 == 5 ? 0 : -1;
3703                        flip = 1-mark*2;
3704                        if (mark >= 0) {
3705                            marklength = min(widths[i]/2, scales[i]*2);
3706                            markwidth = marklength*0.4;
3707                            translate([mark*scales[i], widths[i], 0]) {
3708                                color(colors[1-$idx%2], alpha=alpha) {
3709                                    linear_extrude(height=thickness+scales[i]/100, convexity=2, center=true) {
3710                                        polygon(scale([flip*markwidth, marklength],p=[[0,0], [1, -1], [0,-0.9]]));
3711                                    }
3712                                }
3713                            }
3714                        }
3715                        if (labels && scales[i]/unit+EPSILON >= 1) {
3716                            color(colors[($idx+1)%2], alpha=alpha) {
3717                                linear_extrude(height=thickness+scales[i]/100, convexity=2, center=true) {
3718                                    back(scales[i]*.02) {
3719                                        text(text=str( $idx * scales[i] / unit), size=fontsize, halign="left", valign="baseline");
3720                                    }
3721                                }
3722                            }
3723                        }
3724
3725                    }
3726                }
3727            }
3728            children();
3729        }
3730    }
3731}
3732
3733
3734
3735
3736// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap